Deployment as Code adalah pendekatan di mana langkah deploy tidak lagi bergantung pada runbook manual, perintah ad-hoc, atau ingatan operator, tetapi dinyatakan sebagai kode, manifest, dan aturan yang disimpan di version control. Tujuannya sederhana: rilis harus repeatable, bisa ditinjau lewat pull request, mudah diaudit, dan aman untuk diulang saat kondisi produksi berubah.

Untuk rilis aman, Deployment as Code tidak berhenti di tahap deploy. Seluruh alur perlu dinyatakan secara eksplisit: pre-deploy check, migrasi database yang aman, canary atau batch rollout, health check, observability minimum, rollback terukur, lalu postmortem ringan agar rilis berikutnya lebih baik. Artikel ini fokus pada pola praktis yang bisa langsung dipakai tim kecil, tanpa bergantung pada satu tool tertentu.

Apa yang dimaksud Deployment as Code

Inti idenya mirip dengan konsep Model Training as Code: bukan hanya artefak akhir yang penting, tetapi juga proses yang menghasilkan artefak tersebut. Dalam konteks operasi rilis aplikasi, yang dikodekan bukan cuma definisi infrastruktur, melainkan juga:

  • urutan langkah deploy,
  • aturan validasi sebelum dan sesudah deploy,
  • strategi rollout,
  • kriteria go/no-go,
  • prosedur rollback,
  • pencatatan insiden dan temuan pasca-rilis.

Dengan pendekatan ini, tim mengurangi beberapa sumber kegagalan umum:

  • Drift prosedur: langkah di wiki berbeda dengan yang benar-benar dijalankan.
  • Deploy tribal knowledge: hanya satu atau dua orang yang tahu urutan aman.
  • Rollback improvisasi: saat insiden, tim baru menyusun langkah pemulihan.
  • Postmortem lemah: tidak ada jejak keputusan, sinyal, dan perubahan yang relevan.

Komponen minimum yang perlu dijadikan kode

Untuk tim kecil, target awal yang realistis adalah menjadikan hal-hal berikut sebagai artefak terversi dalam repository:

  • Manifest rilis: versi aplikasi, target environment, strategi rollout, owner, dan referensi perubahan.
  • Pipeline deploy: urutan job dari validasi hingga verifikasi pasca-rilis.
  • Checklist otomatis: pre-deploy check dan post-deploy check yang bisa dieksekusi mesin.
  • Skrip migrasi aman: termasuk validasi kompatibilitas maju/mundur.
  • Aturan rollback: kapan rollback dipicu, langkahnya, dan kondisi yang harus diperiksa sesudahnya.
  • Template incident/postmortem: ringkas, terstruktur, dan mudah diisi.

Jangan mulai dari sistem yang terlalu kompleks. Jika saat ini deploy masih manual, pindahkan dulu alur paling kritis ke pipeline dan manifest sederhana. Nilai utamanya adalah konsistensi, bukan kompleksitas.

Struktur repository dan manifest rilis

Salah satu pola yang mudah dipelihara adalah memisahkan kode aplikasi, definisi deploy, dan template operasional dalam struktur yang jelas.

repo/
├─ app/
├─ deploy/
│  ├─ pipelines/
│  │  └─ release.yaml
│  ├─ manifests/
│  │  ├─ production.yaml
│  │  └─ staging.yaml
│  ├─ scripts/
│  │  ├─ precheck.sh
│  │  ├─ migrate-safe.sh
│  │  ├─ verify.sh
│  │  └─ rollback.sh
│  └─ postmortem/
│     └─ template.md
└─ docs/
   └─ release-policy.md

Contoh manifest rilis yang tetap generik tetapi cukup praktis:

release:
  app: billing-api
  version: 2026.08.02-1a2b3c
  environment: production
  owner: team-backend
  change_window: "09:00-11:00 UTC"

strategy:
  type: canary
  steps:
    - traffic_percent: 10
      hold_minutes: 10
    - traffic_percent: 50
      hold_minutes: 15
    - traffic_percent: 100
      hold_minutes: 0

checks:
  pre_deploy:
    - config_valid
    - db_connectivity
    - queue_backlog_ok
    - error_budget_ok
  post_deploy:
    - readiness_probe_ok
    - error_rate_ok
    - p95_latency_ok
    - key_business_flow_ok

rollback:
  automatic: false
  trigger_signals:
    - elevated_5xx
    - migration_failure
    - key_flow_broken

Manifest seperti ini berguna karena keputusan operasional menjadi eksplisit. Reviewer bisa menilai apakah rollout 10%-50%-100% masuk akal, apakah sinyal verifikasi sudah cukup, dan siapa pemilik rilis tersebut.

Pipeline Deployment as Code yang praktis

Pada implementasi nyata, pipeline tidak harus terikat ke satu CI/CD tertentu. Yang penting adalah tahapannya jelas, idempoten bila memungkinkan, dan gagal secepat mungkin saat syarat tidak terpenuhi.

1. Pre-deploy check

Tahap ini menjawab pertanyaan: apakah sistem layak dirilis sekarang? Banyak insiden sebenarnya terjadi bukan karena build salah, tetapi karena environment sedang tidak sehat saat deploy dimulai.

Checklist minimum yang sebaiknya otomatis:

  • artefak build tersedia dan checksum cocok,
  • konfigurasi penting ada dan valid,
  • konektivitas ke database, cache, dan message broker sehat,
  • backlog queue tidak abnormal,
  • alert kritis yang sedang aktif ditinjau,
  • kapasitas minimum tersedia untuk menampung canary atau batch baru,
  • flag fitur yang diperlukan sudah dalam keadaan benar.

Contoh skrip pre-check sederhana:

#!/usr/bin/env sh
set -eu

echo "[precheck] validate config"
./app/bin/validate-config

echo "[precheck] database connectivity"
./app/bin/check-db

echo "[precheck] cache connectivity"
./app/bin/check-cache

echo "[precheck] queue backlog"
./app/bin/check-queue-backlog --max-pending 1000

echo "[precheck] active incidents"
./app/bin/check-active-alerts --severity critical

echo "[precheck] done"

Mengapa tahap ini penting: deploy ke sistem yang sudah tidak sehat akan mencampur dua variabel sekaligus: masalah existing dan perubahan baru. Akibatnya diagnosis lebih sulit dan rollback belum tentu menyelesaikan akar masalah.

2. Migrasi database yang aman

Migrasi adalah titik rawan karena menyentuh state persisten. Pola aman yang umum dipakai adalah expand-and-contract:

  1. Expand: tambahkan kolom, tabel, atau indeks baru tanpa memutus kompatibilitas.
  2. Deploy aplikasi yang bisa membaca skema lama maupun baru.
  3. Lakukan backfill bila perlu secara terkontrol.
  4. Setelah seluruh traffic stabil, baru contract: hapus kolom atau perilaku lama.

Hindari migrasi yang langsung:

  • menghapus kolom yang masih dipakai versi lama,
  • mengunci tabel besar tanpa evaluasi dampak,
  • menggabungkan perubahan skema dan logika aplikasi yang tidak backward-compatible dalam satu langkah.

Contoh alur migrasi aman dalam pipeline:

#!/usr/bin/env sh
set -eu

echo "[migrate] run forward-compatible migration"
./app/bin/migrate --phase expand

echo "[migrate] verify schema"
./app/bin/check-schema-compat

echo "[migrate] done"

Jika backfill diperlukan, lebih aman menjalankannya sebagai job terpisah dengan pembatasan laju, bukan bagian blocking dari deploy utama, kecuali volumenya kecil dan dampaknya bisa diprediksi.

Catatan: rollback aplikasi tidak selalu berarti rollback database. Untuk perubahan stateful, target realistisnya sering kali adalah roll forward with compatible schema, bukan membalikkan skema secara paksa.

3. Canary atau batch rollout

Setelah artefak lolos pre-check dan migrasi aman selesai, rilis sebaiknya tidak langsung 100%. Untuk tim kecil, dua strategi paling praktis:

  • Canary rollout: kirim sebagian kecil traffic ke versi baru, observasi, lalu naikkan bertahap.
  • Batch rollout: perbarui instance/pod/node dalam kelompok kecil.

Pilih canary jika Anda bisa mengamati trafik produksi secara representatif dan punya metrik layanan yang cukup jelas. Pilih batch rollout jika pembagian traffic sulit tetapi platform mendukung pembaruan bertahap per instance.

Contoh deklarasi rollout dalam pipeline:

stages:
  - precheck
  - migrate
  - deploy_canary
  - verify_canary
  - deploy_half
  - verify_half
  - deploy_full
  - verify_full

verify_rules:
  max_error_rate_delta: "small"
  latency_regression: "within-threshold"
  readiness_required: true

Tidak semua angka harus keras di manifest jika bergantung pada baseline layanan, tetapi kriterianya tetap harus tertulis. Hindari aturan samar seperti “lihat dashboard dulu” tanpa definisi sinyal apa yang diperiksa.

4. Health check pasca-rilis

Health check yang berguna tidak cukup hanya memeriksa proses hidup. Minimal bedakan tiga jenis pemeriksaan:

  • Liveness: proses tidak macet total.
  • Readiness: instance siap menerima trafik.
  • Functional check: alur bisnis utama tetap berjalan, misalnya login, checkout, atau pembuatan invoice.

Kesalahan umum adalah hanya mengandalkan endpoint /health yang selalu mengembalikan status sukses meski dependensi utama gagal. Untuk verifikasi rilis, tambahkan pemeriksaan alur bisnis yang paling penting namun aman dijalankan berulang.

#!/usr/bin/env sh
set -eu

echo "[verify] readiness"
./app/bin/check-readiness

echo "[verify] error rate"
./app/bin/check-metrics --signal http_5xx

echo "[verify] latency"
./app/bin/check-metrics --signal latency_p95

echo "[verify] key flow"
./app/bin/smoke-test --flow create_invoice

echo "[verify] done"

Observability minimum untuk keputusan go/no-go

Deployment as Code membutuhkan sinyal yang bisa dipercaya. Tanpa itu, pipeline hanya memindahkan prosedur manual ke YAML tanpa benar-benar meningkatkan keamanan rilis.

Log

Pastikan log aplikasi minimal memuat:

  • timestamp yang konsisten,
  • service name dan environment,
  • release/version identifier,
  • request ID atau correlation ID,
  • severity,
  • pesan error yang cukup jelas.

Versi rilis di log penting untuk memisahkan error lama dan error yang muncul setelah canary aktif.

Metric

Untuk kebanyakan layanan web/API, empat kelompok metrik sudah sangat membantu:

  • traffic: request rate atau job throughput,
  • errors: 5xx, exception, job failure,
  • latency: p50/p95 atau metrik respons serupa,
  • saturation: CPU, memory, connection pool, queue lag.

Selain metrik teknis, tambahkan key business metric bila memungkinkan, misalnya rasio pembayaran berhasil atau jumlah order sukses per menit. Kadang layanan tampak sehat secara HTTP, tetapi alur bisnis utama ternyata rusak.

Trace

Trace membantu saat error hanya muncul di rantai permintaan tertentu, misalnya API memanggil beberapa layanan atau query database tambahan. Untuk tim kecil, target realistisnya bukan observability sempurna, melainkan kemampuan menjawab pertanyaan: permintaan mana yang lambat, service mana yang menambah error, dan apakah versi baru mengubah pola itu?

Alert

Alert yang dipakai saat rollout harus sedikit tetapi relevan. Jika terlalu banyak, operator akan bingung. Buat daftar sinyal go/no-go yang eksplisit.

Checklist sinyal go/no-go

  • Go jika readiness sukses, error rate tidak naik signifikan, latensi tetap dalam ambang normal, dan alur bisnis utama lolos smoke test.
  • No-go jika ada lonjakan 5xx, timeout meningkat jelas, backlog queue naik terus, migrasi gagal, atau alarm bisnis utama aktif.
  • Pause jika sinyal belum konklusif, misalnya volume canary terlalu kecil atau dashboard observability belum stabil.

Keputusan pause sering lebih sehat daripada memaksa lanjut atau rollback terlalu cepat. Tujuannya adalah mengurangi tindakan impulsif saat sinyal belum cukup.

Rollback terukur, bukan panik

Rollback yang baik adalah rollback yang sudah didefinisikan sebelum deploy berjalan. Pipeline harus tahu:

  • komponen apa yang bisa dibalik cepat,
  • komponen apa yang tidak aman dibalik,
  • sinyal apa yang memicu rollback,
  • siapa yang berwenang mengeksekusi atau menyetujui,
  • verifikasi apa yang harus dijalankan setelah rollback.

Contoh skrip rollback aplikasi:

#!/usr/bin/env sh
set -eu

PREVIOUS_RELEASE="$1"

echo "[rollback] switch traffic to ${PREVIOUS_RELEASE}"
./app/bin/deploy --version "${PREVIOUS_RELEASE}" --strategy immediate

echo "[rollback] verify readiness"
./app/bin/check-readiness

echo "[rollback] verify key flow"
./app/bin/smoke-test --flow create_invoice

echo "[rollback] done"

Trade-off penting:

  • Rollback cepat mengurangi durasi dampak, tetapi bisa menutupi akar masalah jika investigasi tidak segera dimulai.
  • Menahan rollout pada 10% atau 50% memberi waktu observasi lebih baik, tetapi memperlama keadaan dua versi berjalan bersamaan.
  • Rollback kode mudah dilakukan pada layanan stateless, tetapi lebih rumit bila sudah ada migrasi stateful atau perubahan kontrak antar-layanan.

Kesalahan umum saat rollback:

  • rollback kode tetapi lupa kompatibilitas schema,
  • mengabaikan pekerjaan asinkron yang masih diproses worker versi baru,
  • tidak memeriksa cache, flag, atau konfigurasi yang sudah berubah saat rilis,
  • menganggap rollback pasti mengembalikan sistem ke kondisi semula.

Template pipeline end-to-end yang bisa dijadikan titik awal

Berikut contoh struktur pipeline yang sederhana namun lengkap secara operasional:

  1. Build & package: buat artefak immutable dan tandai versi rilis.
  2. Static validation: lint, unit test, validasi manifest deploy.
  3. Pre-deploy check: cek health environment dan dependensi.
  4. Safe migration: jalankan perubahan schema yang kompatibel maju.
  5. Deploy canary/batch awal: rilis sebagian kecil.
  6. Verify: readiness, metrik, log error, smoke test bisnis.
  7. Promote bertahap: naikkan traffic atau jumlah instance.
  8. Final verification: pastikan sinyal stabil pasca 100% rollout.
  9. Close release: simpan ringkasan hasil, link dashboard, dan keputusan final.
  10. Incident branch: jika gagal, jalankan rollback dan buat catatan insiden otomatis.

Pola ini bekerja karena memaksa pemisahan antara aksi dan keputusan. Aksi dikerjakan pipeline; keputusan go/no-go diambil berdasarkan sinyal yang juga telah didefinisikan sebagai kode atau aturan versioned.

Postmortem ringan yang tetap berguna

Banyak tim kecil menghindari postmortem karena terkesan formal dan berat. Padahal setelah rollback atau insiden kecil pun, catatan singkat sangat berharga agar pola kegagalan tidak berulang. Postmortem ringan cukup berisi fakta, dampak, keputusan, dan tindak lanjut.

Contoh template:

# Postmortem Rilis

- Layanan:
- Versi rilis:
- Waktu mulai:
- Waktu terdeteksi:
- Waktu pulih:
- Dampak ke pengguna:
- Gejala utama:
- Sinyal yang memicu pause/rollback:
- Perubahan yang terkait:
- Akar masalah sementara:
- Mengapa lolos dari pre-check/test:
- Tindakan pemulihan:
- Action item pencegahan:
  - [ ] Tambah check di pipeline
  - [ ] Tambah alert/metrik
  - [ ] Ubah strategi migrasi
  - [ ] Tambah smoke test bisnis
  - [ ] Dokumentasikan constraint baru

Yang penting bukan panjang dokumennya, tetapi kualitas pembelajaran. Jika insiden terjadi karena smoke test tidak mencakup alur tertentu, maka perbaikannya harus masuk ke kode pipeline, bukan hanya dicatat di dokumen.

Tindakan pencegahan agar rilis berikutnya lebih aman

Deployment as Code memberi manfaat terbesar jika setiap insiden mengubah sistem rilis menjadi lebih kuat. Beberapa perbaikan yang biasanya paling berdampak untuk tim kecil:

  • Tambahkan check yang benar-benar gagal di insiden sebelumnya, bukan menambah checklist generik.
  • Gunakan feature flag untuk memisahkan aktivasi fitur dari deploy, terutama untuk perubahan berisiko tinggi.
  • Jaga kompatibilitas mundur pada API, schema, dan event sebisa mungkin.
  • Jadikan versi rilis terlihat di log, metric, dan dashboard agar korelasi lebih cepat.
  • Batasi ukuran perubahan per rilis; rilis kecil lebih mudah didiagnosis dan dibalik.
  • Latih rollback di staging atau game day sederhana agar prosedur tidak hanya ada di repository.
  • Definisikan ownership yang jelas: siapa release owner, siapa reviewer, dan siapa incident commander bila perlu.

Kesalahan implementasi yang sering terjadi

Menganggap YAML saja sudah cukup

Deployment as Code bukan sekadar menaruh langkah deploy di file konfigurasi. Jika verifikasi, sinyal, dan rollback tidak ikut didefinisikan, Anda baru memindahkan masalah ke tempat lain.

Terlalu cepat mengotomatisasi semuanya

Untuk tim kecil, lebih baik punya satu pipeline yang sederhana tetapi reliabel daripada sistem penuh cabang kondisi yang sulit dipahami. Otomasikan keputusan yang punya sinyal jelas; sisakan persetujuan manual untuk area yang masih abu-abu.

Tidak memisahkan deploy dan release

Deploy artefak ke produksi tidak selalu harus langsung mengaktifkan seluruh perilaku baru. Feature flag, progressive rollout, atau endpoint yang nonaktif dulu dapat mengurangi radius dampak.

Mengabaikan state dan pekerjaan asinkron

Aplikasi mungkin stateless, tetapi sistemnya belum tentu. Queue worker, scheduler, cache, dan migrasi data sering menjadi sumber inkonsistensi saat rollout dan rollback.

Penutup

Deployment as Code untuk rilis aman, rollback, dan postmortem adalah cara menjadikan operasi rilis lebih disiplin tanpa harus membangun platform besar. Mulailah dari hal yang paling sering menyebabkan masalah: pre-check, migrasi aman, rollout bertahap, health check yang bermakna, observability minimum, dan rollback yang sudah ditulis sebelum insiden terjadi.

Jika setiap rilis dinyatakan sebagai kode dan setiap insiden menghasilkan perubahan nyata pada pipeline atau manifest, tim kecil pun bisa membangun proses rilis yang konsisten, bisa diaudit, dan makin aman dari waktu ke waktu.