Backup menjawab musibah yang datang tanpa diundang; rilis adalah musibah yang kita jadwalkan sendiri. Setiap docker compose up -d yang mengganti image produksi adalah momen ketika toko bertaruh versi baru di depan pelanggan sungguhan, dan cara kita menekan taruhan itu menentukan apakah rilis terasa seperti ritual yang tenang atau lemparan dadu di jam sibuk. Kabar baiknya: seluruh infrastruktur yang kita bangun sepanjang section ini — image bertag di registry dengan digest yang bisa diverifikasi, healthcheck tiga lapis, restart policy, proxy dengan resolver dinamis, backup yang berjalan tiap malam — ada persis untuk momen ini. Chapter ini menyusun urutannya: rilis paling sederhana lewat pull dan up -d, pola blue-green untuk nol detik henti, rollback dalam hitungan menit, lalu dua disiplin yang membedakan rilis dewasa dari pertaruhan — menghormati migrasi database yang tidak mau mundur sendiri, dan membukti-bukti rilis dengan verifikasi terukur sebelum menyebutnya selesai.

Rilis Versi Baru: pull dan up -d #

Siti menyelesaikan fitur pesanan bernomor, CI membangun dan mendorong registry.example.com/toko/backend:1.5.0, dan satu commit di repositori toko memperbarui tag image pada compose.yaml. Seluruh perubahan itu berakhir di server sebagai dua perintah:

bash
$ cd /srv/toko
$ git pull
$ docker compose pull

Output yang Dihasilkan (dipotong):

text
1.5.0: Pulling from toko/backend
a1b2c3d4e5f6: Pull complete
7e8f9a0b1c2d: Pull complete
Digest: sha256:7a2e9c4b1d6f83025a7c9e1b3d5f7092a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2d6
Status: Downloaded newer image for registry.example.com/toko/backend:1.5.0
[+] Pulling 5/5
 ✔ db Pulled       3.1s
 ✔ cache Pulled    1.8s
 ✔ proxy Pulled    2.4s
 ✔ frontend Pulled 2.0s
 ✔ backend Pulled  6.2s

Lima service ikut diperiksa ke registry, tetapi hanya backend yang membawa layer baru — empat lainnya dijawab up-to-date dalam hitungan detik, bukti lagi bahwa content-addressable membuat registry tahu persis blob mana yang belum dimiliki mesin ini. Giliran penggantian:

bash
$ docker compose up -d

Output yang Dihasilkan:

text
[+] Running 5/5
 ✔ Container toko-db-1        Running    0.0s
 ✔ Container toko-cache-1     Running    0.0s
 ✔ Container toko-backend-1   Recreated  1.8s
 ✔ Container toko-frontend-1  Running    0.0s
 ✔ Container toko-proxy-1     Running    0.0s

Mari kita telaah kosakata status pada keluaran tersebut — dua kata yang membawa dua nasib berbeda:

  1. Running untuk empat service — Compose membandingkan cetakbiru compose.yaml dengan container yang ada; image dan konfigurasi yang tak berubah membuat Compose membiarkan container tetap hidup. Database tidak disentuh sama sekali: koneksi pool, isi volume, dan status healthy semuanya meneruskan hidupnya.
  2. Recreated untuk backend — satu-satunya service yang image-nya berganti digest; container lama menerima SIGTERM dengan masa tenggang stop_grace_period, dihapus, lalu container baru lahir dengan nama toko-backend-1 yang sama.
  3. Jeda yang terasa hanya 1-2 detik pada jalur /api/ — selama jeda itu proxy mendapat 502 sesaat; begitu container baru terdaftar di DNS Compose, resolver 127.0.0.11 dan proxy_pass berbasis variabel dari Chapter Reverse Proxy dan TLS menemukan alamat barunya tanpa perlu me-restart proxy.
  4. Gerbang tetap berlaku untuk bayi baru — depends_on dengan condition: service_healthy menahan backend baru sampai db membuktikan sehat, dan healthcheck backend sendiri menaikkan status (healthy) dalam belasan detik.

Status akhirnya dibaca dalam satu tabel:

bash
$ docker compose ps

Output yang Dihasilkan:

text
NAME             IMAGE                                        COMMAND                  SERVICE   CREATED             STATUS                      PORTS
toko-proxy-1     nginx:1.29-alpine                            "/docker-entrypoint.…"   proxy     7 days ago          Up 7 days                   0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, ...
toko-frontend-1  registry.example.com/toko/frontend:1.4.0      "/docker-entrypoint.…"   frontend  7 days ago          Up 7 days                   80/tcp
toko-backend-1   registry.example.com/toko/backend:1.5.0       "docker-entrypoint.s…"   backend   About a minute ago Up About a minute (healthy) 3000/tcp
toko-db-1        postgres:18-alpine                            "docker-entrypoint.s…"   db        7 days ago          Up 7 days (healthy)         127.0.0.1:5432->5432/tcp
toko-cache-1     redis:7-alpine                                "docker-entrypoint.s…"   cache     7 days ago          Up 7 days                   6379/tcp

Empat baris berumur tujuh hari dan satu baris berumur semenit — itulah seluruh jejak rilis pada tabel produksi yang sehat. Kolom CREATED yang mencampur umur panjang dan umur semenit adalah tanda bahwa penggantian mengenai sasaran: aplikasi berganti, fondasi tetap.

Blue-Green: Dua Warna Berdampingan #

Jeda 1-2 detik pada rilis sederhana itu bisa diterima toko pada hari biasa — tetapi bukan saat promosi weekend dengan ratusan keranjang aktif. Untuk momen itu, pola blue-green menaruh versi baru berdampingan dengan versi lama, menguji yang baru secara privat, lalu memindahkan pintu proxy dalam satu detik tanpa memutus satu koneksi pun. Desainnya membagi stack menjadi tiga project Compose: project toko tetap memegang db, cache, dan proxy — wasit yang tidak pernah berganti — sementara aplikasi hidup di project berwarna toko-biru dan toko-hijau yang bergantian menjadi yang aktif. Berkas compose.warna.yaml adalah lapisan yang membuat pola ini bekerja:

yaml
services:
  backend:
    networks:
      backend-net:
        aliases:
          - backend-${WARNA}

  frontend:
    networks:
      frontend-net:
        aliases:
          - frontend-${WARNA}

networks:
  backend-net:
    external: true
    name: toko_backend-net
  frontend-net:
    external: true
    name: toko_frontend-net

Mari kita telaah tiga keputusan pada berkas kecil tersebut:

  1. Alias backend-${WARNA} — container backend project warna apapun mendaftarkan dua nama di DNS network: nama biasa dan alias berwarna; proxy memanggil nama berwarna sehingga berganti warna tidak pernah menimpa nama lawannya.
  2. external: true dengan name: eksplisit — kedua network dimiliki project toko (nama aslinya toko_backend-net dan toko_frontend-net); project warna hanya menumpang, sehingga container hijau dan biru hidup di halaman yang sama dengan proxy dan db — pola klaim nama eksplisit yang kita tanam di Chapter Persistent Storage Production, kini dipakai untuk network.
  3. Variabel WARNA — satu berkas yang sama melahirkan dua project berbeda lewat lingkungan; tidak ada compose.yaml duplikat yang harus disinkronkan.

Adopsi pola ini dari model tunggal hanya butuh dua gerakan: lahirkan warna biru dengan versi yang sedang berjalan, lalu cabut aplikasi dari project induknya.

bash
$ WARNA=biru docker compose -f compose.yaml -f compose.warna.yaml -p toko-biru up -d backend frontend
$ docker compose rm -sf backend frontend

Output yang Dihasilkan:

text
[+] Running 2/2
 ✔ Container toko-biru-backend-1   Started
 ✔ Container toko-biru-frontend-1  Started
[+] Running 2/2
 ✔ Container toko-backend-1   Removed
 ✔ Container toko-frontend-1  Removed

Proxy kini menunjuk ke backend-biru lewat satu kata pada nginx.conf (set $backend_host backend-biru; dan pasangannya set $frontend_host frontend-biru;) — perubahan yang sama dengan yang akan kita lakukan saat ganti warna. Sepekan kemudian, rilis 1.6.0 datang dan hijau naik panggung tanpa menyentuh biru:

bash
$ git pull
$ WARNA=hijau docker compose -f compose.yaml -f compose.warna.yaml -p toko-hijau up -d backend frontend

Output yang Dihasilkan:

text
[+] Running 2/2
 ✔ Container toko-hijau-backend-1   Started
 ✔ Container toko-hijau-frontend-1  Started

Dua warna kini hidup bersamaan — biru melayani pelanggan, hijau menyala tapi belum menerima satu pengunjung pun. Ujian terhadap hijau dilakukan dari dalam network, jauh sebelum pelanggan bisa tersesat ke sana:

bash
$ docker run --rm --network toko_backend-net curlimages/curl:8.14.1 \
    -fsS http://backend-hijau:3000/healthz

Output yang Dihasilkan:

text
ok

Container uji memakai image curl murni, hidup sesaat di toko_backend-net, dan mengetuk alias backend-hijau — nama yang hanya dijawab oleh container hijau; flag -f membuat curl keluar galat bila jawaban bukan 2xx, sehingga jawaban ok di atas adalah bukti sehat, bukan dugaan. Uji lebih dalam tinggal mengulang pola yang sama ke rute sungguhan seperti /api/produk.json. Puncaknya satu kata di nginx.conf diganti — backend-biru menjadi backend-hijau, frontend-biru menjadi frontend-hijau — lalu proxy membaca ulang konfigurasinya:

bash
$ docker compose exec proxy nginx -t
$ docker compose exec proxy nginx -s reload

Output yang Dihasilkan:

text
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
2026/10/31 10:12:03 [notice] 3011#3011: signal process started

Biru yang telanjur kosong melenggang turun tanpa menyentuh siapa-siapa:

bash
$ docker compose -p toko-biru down

Output yang Dihasilkan:

text
[+] Running 2/2
 ✔ Container toko-biru-backend-1   Removed
 ✔ Container toko-biru-frontend-1  Removed

Perintah down pada project warna hanya membawa container warnanya sendiri — db, cache, proxy, dan kedua volume produksi tidak tersentuh karena semuanya milik project lain. Satu harga yang harus dibayar jujur: selama masa tumpang tindih, host menanggung dua kali beban aplikasi; resource limit per service yang kita pasang dari Chapter CPU dan Memory Limit menjaga masa itu tetap terukur, dan durasinya hanya seleksi uji — beberapa menit, bukan beberapa jam.

Rollback dalam Hitungan Menit #

Rilis yang buruk menampar cepat: error rate naik, docker compose logs --since 5m dipenuhi kalimat merah, healthcheck bolak-balik. Kabar baiknya, arah mundur adalah urutan yang sama dengan arah maju — dengan tag yang lebih tua:

bash
$ git checkout v1.4.0
$ docker compose pull
$ docker compose up -d

Output yang Dihasilkan:

text
[+] Running 5/5
 ✔ Container toko-db-1        Running    0.0s
 ✔ Container toko-cache-1     Running    0.0s
 ✔ Container toko-backend-1   Recreated  1.5s
 ✔ Container toko-frontend-1  Running    0.0s
 ✔ Container toko-proxy-1     Running    0.0s

Perintah git checkout v1.4.0 mengembalikan compose.yaml ke titik rilis lama, pull memastikan image 1.4.0 ada, dan up -d melakukan Recreated ke arah yang kita maksud. Tiga generasi image di host menjadi jaminan bahwa arah mundur tidak menunggu jaringan:

bash
$ docker images registry.example.com/toko/backend

Output yang Dihasilkan:

text
REPOSITORY                        TAG      IMAGE ID       CREATED         SIZE
registry.example.com/toko/backend 1.5.0    9d2f6a1c8e4b   3 hours ago     142MB
registry.example.com/toko/backend 1.4.0    6b8e0d2f4a7c   6 days ago      139MB
registry.example.com/toko/backend 1.3.0    3a5c7e9b1d0f   3 weeks ago     131MB

Tabel itu menampung rollback lokal instan; registry menjadi lapis kedua saat host berganti atau disk di-prune; dan kebijakan retensi tag di registry — tag lama tidak dihapus, minimal N-3 generasi — adalah lapis ketiga. Untuk rilis yang benar-benar kritis, kunci deployment tidak pada tag melainkan pada digest yang tidak bisa dimutasi, persis pelajaran Chapter Image Digest:

yaml
services:
  backend:
    image: registry.example.com/toko/backend@sha256:3f8c1a9d2e4b607159c28ad3f0b4e6d8a1c3e5f7b9d0f2a4c6e8b0d2f4a6c8e0

Bentuk registry/repo@sha256:... meminum image versi 1.4.0 apa adanya selamanya — tag 1.4.0 bisa ditimpa siapa pun yang punya hak push, digest tidak; itulah alasan CHANGELOG kita mencatat digest di setiap rilis.

Empat pertanyaan berikut dijawab sebelum tombol deploy ditekan — tabel uji pra-rilis yang membuat rollback sebagai rencana, bukan harapan:

Pertanyaan sebelum rilisCara mengujinya
Apakah skema baru aman bagi kode lama?jalankan image lama melawan skema baru di staging — expand-contract menjamin jawaban ya
Apakah config lama masih dibaca tanpa galat?variabel baru punya nilai default; variabel lama dihapus satu rilis kemudian
Apakah image versi lama masih tersedia?docker images lokal menampilkan generasi; tag lama masih terdaftar di registry
Apakah runbook rollback tertulis dan pernah dilatih?lembar drill terakhir berumur kurang dari satu kuartal

Verifikasi Pasca-Deploy #

Rilis baru dinyatakan sehat oleh bukti, bukan oleh ketiadaan keluhan. Kerangka lima langkah Chapter Strategi Diagnostik menuntun bentuk buktinya: gejala presisi, lalu fakta terukur. Tiga perintah menutup setiap rilis toko. Yang pertama sudah kita lihat — docker compose ps dengan CREATED semenit dan STATUS (healthy). Yang kedua mengukur jalur publik lima kali berturut:

bash
$ for i in 1 2 3 4 5; do curl -fsSk -o /dev/null -w "%{http_code} %{time_total}s\n" https://localhost/api/produk.json; sleep 2; done

Output yang Dihasilkan:

text
200 0.042s
200 0.039s
200 0.041s
200 0.038s
200 0.040s

Lima jawaban 200 dengan latensi stabil di bawah 50 milidetik adalah denyut sehat yang tidak menurun — satu permintaan yang lambat bisa kebetulan, lima yang stabil adalah pola. Perintah ketiga menyisir log jendela lima menit untuk kata yang tak boleh hadir:

bash
$ docker compose logs --since 5m backend | grep -ci error

Output yang Dihasilkan:

text
0

Angka nol dari grep -ci menutup ritual: jalur publik hijau, container sehat, log bersih — rilis boleh disebut selesai dan CHANGELOG boleh ditulis.

Changelog dan Jejak Rilis #

Jejak tertulis membuat rilis berikutnya lebih pintar dari rilis sebelumnya. Tag beranotasi menandai titik mundur yang persis:

bash
$ git tag -a v1.5.0 -m "pesanan: nomor pesanan + trace_id di log"
$ git tag -l

Output yang Dihasilkan:

text
v1.3.0
v1.4.0
v1.5.0

CHANGELOG.md merangkum tiap tag dengan informasi yang dibutuhkan saat panik — apa yang berubah, image apa yang terpasang, dan seberapa aman arah mundurnya:

text
## v1.5.0 — 2026-10-24
- Pesanan kini bernomor urut dan mencatat trace_id di log backend.
- Migrasi: kolom nomor_pesanan DITAMBAHKAN (nullable — tahap expand).
- image backend: registry.example.com/toko/backend@sha256:3f8c1a9d2e4b6071...
- Rollback: aman ke v1.4.0 (kolom baru nullable; image 1.4.0 masih di registry).

Seluruh chapter ini terdestilasi menjadi tujuh langkah rilis yang bisa ditempel di dinding:

#LangkahGerbang lulus
1Build dan test di mesin CIseluruh test hijau sebelum image dibuat
2Scan image hasil buildtidak ada critical/high baru
3Push image bertag, catat digestdigest masuk CHANGELOG
4Perbarui compose.yaml, commit, git tag -atag menunjuk commit yang tepat
5docker compose pulldigest yang ditarik sama dengan yang dicatat
6docker compose up -dhanya service yang berubah yang Recreated
7Verifikasi: ps healthy, loop curl, grep errorlima 200 stabil, hitungan error nol

Tujuh langkah itu berjalan di atas satu mesin bernama srv-toko yang sejauh ini kita perlakukan seperti sudah ada — padahal seluruh section ini dimulai dari server yang masih kosong. Bagaimana VPS dikeraskan sebelum Docker dipasang, mengapa ufw bisa dibocorkan Docker diam-diam, bagaimana stack pertama dideploy sampai curl menjawab HTTP/2, dan ritual pemeliharaan apa yang menjaga server itu sehat berbulan-bulan — semuanya adalah isi Chapter Docker di VPS.

Referensi #