Rahasia toko kini menempati tempat yang benar di seluruh daur hidupnya, dan compose.yaml produksi sudah memuat hampir semua kunci yang kita impikan. Namun mengamankan barang adalah pekerjaan yang berbeda dari menjaga napas: stack yang rahasianya tertata tetap bisa mati diam-diam oleh crash tengah malam, oleh server yang reboot saat pemadaman listrik, atau oleh aplikasi yang menggantung tanpa menjawab sambil kolom STATUS tersenyum Up. Chapter ini merakit ketahanan produksi menjadi resep tiga lapis — probe yang jujur tertanam di image, kebijakan bangkit yang dideklarasikan di deployment, dan kalibrasi ritme yang menimpanya di compose — lalu menguji resep itu dari dua arah: stack yang menyala sehat dengan urutan yang terbaca, dan stack yang jatuh sakit dengan kronologi yang tercatat.
Tiga Lapis Ketahanan #
Satu mekanisme tidak akan pernah cukup, karena tiga musuh kita bekerja pada waktu yang berbeda. Aplikasi yang menggantung menipu docker ps dalam hitungan menit; crash membunuh proses dalam hitungan detik; reboot host menghapus semua container dalam sekejap. Tiga lapis berikut masing-masing menjawab satu musuh:
| Lapis | Tempat hidup | Musuh yang dijawab | Diatur oleh |
|---|---|---|---|
| HEALTHCHECK di image | Dockerfile | aplikasi menggantung — proses hidup tapi tak melayani | pembuat image |
restart: unless-stopped | compose.yaml | crash proses dan reboot host | operator deployment |
| healthcheck override di compose | compose.yaml | ritme deteksi yang kurang pekat untuk produksi | operator deployment |
Pembagian perannya tegas: image membawa kejujuran, deployment membawa kebijakan. Pembuat image paling tahu bagaimana cara menanyakan “masih sanggup melayani?” kepada aplikasinya — jawaban itu ikut ke mana-mana bersama image. Operator deployment paling tahu berapa cepat insiden harus terdeteksi di lingkungannya dan kapan container layak dibangkitkan ulang — keputusan itu tidak boleh memaksa image dibangun ulang. Kombinasi keduanya memberi Compose kata terakhir tanpa menyentuh Dockerfile, persis perilaku penimpaan yang kita kenal dari Chapter Healthcheck dan Service Readiness.
Merancang /healthz yang Jujur #
Endpoint /healthz sudah menemani toko sejak Chapter Startup dan Dependency, dan bentuk produksinya di server.js 1.0.0 hanya berganti satu angka dari versi pengembangannya:
if (req.url === "/healthz") {
try {
await pool.query("SELECT 1");
res.writeHead(200).end("ok");
} catch {
res.writeHead(503).end("db tidak siap");
}
return;
}Mari kita telaah empat keputusan pada endpoint tersebut:
pool.query("SELECT 1")— kueri termurah yang tetap menempuh jalur lengkap dari handler, ke pool, ke network, sampai database dan kembali; bila salah satu mata rantai putus, baris ini melempar galat sebelum jawaban sempat terkirim.503menggantikan500— Service Unavailable adalah kode yang jujur untuk kondisi “proses hidup, layanan belum sanggup”;500menyiratkan galat program, padahal di sini aplikasi sehat dan justru sedang melaporkan bahwa tetangganya sakit. Keduanya sama-sama gagal di mata probe — wget hanya mengenal 2xx sebagai lulus — tetapi kode yang benar membuat log dan dashboard bisa dibaca manusia.catchtanpa variabel — galat ditelan dengan sengaja: probe bertugas melaporkan, bukan memperbaiki, dan yang paling penting bukan menjatuhkan proses; galat tak tertangani di handler justru mematikan backend yang sedang sehat.returndi tiap cabang — satu jalur keluar per permintaan; setelah menjawab 200 atau 503, handler selesai dan tidak jatuh ke rute lain.
Satu catatan untuk masa depan: dunia orchestration memisahkan dua pertanyaan yang kita gabungkan dalam satu endpoint. Liveness bertanya “apakah prosesnya hidup?” — jawaban salah berarti restart; readiness bertanya “apakah sanggup melayani?” — jawaban salah berarti sementara dikeluarkan dari rotasi tanpa dibunuh. /healthz kita adalah readiness: db yang mati membuat backend menjawab 503, dan itu berarti “jangan kirim saya pelanggan”, bukan “bunuh saya”. Kubernetes memisahkan keduanya sebagai probe terpisah, sementara Swarm tetap berpegang pada satu healthcheck — perbedaan yang kita bahas tuntas di Section Orchestration dan Ekosistem.
Menanam HEALTHCHECK di Image #
Lapis pertama sudah tertanam sejak image dibangun pada chapter pembuka section ini:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/healthz || exit 1Recap singkat dari Chapter HEALTHCHECK: --timeout=3s menjauhi nilai bawaan 30 detik yang membuat probe menggantung terbaca lama; --start-period=10s adalah masa hangar ketika kegagalan tidak dihitung, memberi Node waktu membaca environment dan membuka pool sebelum dinilai; --retries=3 menuntut tiga kegagalan beruntun sebelum status turun. Setiap container dari image ini — di mana pun ia berjalan — diperiksa dengan cara yang sama, dan kolom STATUS berhenti berbohong sejak container pertama lahir.
Restart Policy untuk Produksi #
Lapis kedua menjawab kematian proses. Empat kebijakan yang kita bedah pada Chapter Restart Policy bermuara pada satu pilihan untuk produksi:
restart: unless-stoppedAlasannya adalah tiga perilaku sekaligus. Setelah crash, container dibangkitkan ulang dengan jeda backoff yang dimulai 100 milidetik dan berlipat ganda pada setiap percobaan — crash loop yang kacau menjadi deret kebangkitan yang melambat dan memberi sistem ruang bernapas. Setelah reboot host, daemon yang baru menyala membangkitkan kembali container yang terakhir berjalan — skenario pemadaman dini hari tertangani tanpa satu pun perintah manusia, selama image tersimpan lokal. Dan saat operator menghentikan service dengan sengaja, keputusan itu dihormati permanen: always akan membangkitkan container kembali setelah daemon restart meski operator sudah menghentikannya, sedangkan unless-stopped taat — dan ketaatan kepada manusia adalah perilaku yang benar untuk mesin produksi.
Lapis kedua ini hanya bekerja bila aplikasi berani mati. Restart policy bereaksi terhadap exit code, jadi process.exit(1) yang cepat dari validasi konfigurasi pada Chapter Konfigurasi Production adalah kolaborator sempurna: kegagalan menjadi kejadian yang terlihat, daemon membangkitkan dengan backoff, dan log galat yang jelas tercetak berulang sampai seseorang memperbaikinya. Sebaliknya, aplikasi yang menggantung saat kondisinya tak tertolong mengambil alih seluruh drama: PID 1 tetap hidup, restart policy tidak pernah terpicu, dan stack tampak sehat sambil tidak melayani siapa pun. Aturan praktisnya dua sisi: gagal cepat untuk kondisi yang tidak mungkin pulih — konfigurasi hilang, berkas kredensial tak terbaca — dan coba ulang untuk kondisi yang bisa pulih, seperti db yang sedang dibangkitkan; retry dengan backoff di sisi aplikasi tetap merupakan sahabat terbaik restart policy, persis pelajaran Chapter Service Discovery.
Menyatukan Lapis di Compose #
Ketiga lapis kini bertemu dalam satu deklarasi service backend yang lengkap — bentuk final yang akan menemani toko sampai akhir section ini:
backend:
image: registry.example.com/toko/backend:1.0.0
user: "1000:1000"
env_file:
- env/${APP_ENV:-dev}.env
secrets:
- source: db_password
target: db_password
uid: "1000"
gid: "1000"
mode: 0400
read_only: true
tmpfs:
- /tmp
restart: unless-stopped
stop_grace_period: 20s
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/healthz"]
interval: 15s
timeout: 3s
retries: 3
start_period: 10s
start_interval: 5sMari kita telaah empat keputusan yang menyatukan ketahanan di atas:
- Blok
healthcheckmenimpa probe image —intervaldirapatkan dari 30s milik image menjadi 15s danstart_interval: 5smempercepat pemeriksaan selama masa hangar; deteksi insiden produksi jadi dua kali lebih cepat tanpa satu pun byte image dibangun ulang — inilah hak kata terakhir operator. stop_grace_period: 20s— dua puluh detik upacara penutupan yang sudah teruji di Chapter Graceful Shutdown: SIGTERM diterimanodesebagai PID 1, permintaan yang sedang digarap dituntaskan, pool ditutup dengan sopan, keluar dengan kode 0.secretsdenganmode: 0400danuser: "1000:1000"— ketahanan tidak membeli kerahasiaan dengan mengorbankannya: kredensial tetap hanya terbaca oleh usernode, dan filesystem tetap beku kecuali/tmp.- Dua gerbang
service_healthy— urutan hidup stack produksi sama ketatnya dengan versi pengembangannya: db dan cache harus membuktikan kesiapan sebelum backend dinyalakan, dan backend harus sehat sebelum proxy membuka pintu.
Menyalakan Stack dan Membaca Statusnya #
Stack dibongkar bersih dengan docker compose down — volume db-data tetap utuh — lalu dinyalakan penuh untuk menyaksikan rantai ketahanan bekerja dari nol:
$ docker compose up -dOutput yang Dihasilkan:
[+] Running 6/6
✔ Network toko-produksi_backend-net Created 0.2s
✔ Network toko-produksi_frontend-net Created 0.2s
✔ Container toko-produksi-db-1 Healthy 4.8s
✔ Container toko-produksi-cache-1 Healthy 1.9s
✔ Container toko-produksi-backend-1 Healthy 12.6s
✔ Container toko-produksi-proxy-1 Started 0.6sEnam sumber daya hadir dalam urutan yang bisa dibaca seperti naskah. Database memakai 4,8 detik untuk melewati pemulihan dan probe pg_isready; cache melengkapi dalam 1,9 detik dengan PONG; backend memakan 12,6 detik — sebagian besar adalah start_period 10 detik yang sengaja diberikan sebagai masa hangar, dengan start_interval 5 detik mengangkat status ke healthy segera setelah /healthz pertama menjawab; dan proxy menyala paling akhir karena pintu hanya layak dibuka setelah isi rumah siap. Daftar stack menegaskan keadaan akhirnya:
$ docker compose psOutput yang Dihasilkan:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
toko-produksi-db-1 postgres:18-alpine "docker-entrypoint.s…" db 2 minutes ago Up 2 minutes (healthy)
toko-produksi-cache-1 redis:7-alpine "docker-entrypoint.s…" cache 2 minutes ago Up 2 minutes (healthy)
toko-produksi-backend-1 registry.example.com/toko/backend:1.0.0 "node server.js" backend 2 minutes ago Up About a minute (healthy)
toko-produksi-proxy-1 nginx:1.29-alpine "nginx -g 'daemon off…" proxy 2 minutes ago Up About a minute 0.0.0.0:8080->80/tcp, [::]:8080->80/tcpKetika Backend Menjadi Unhealthy #
Ujian ketahanan yang sesungguhnya datang dari keadaan sakit, dan Siti mengarang satu skenario latihan: db dihentikan sementara di siang yang sepi, lalu dinyalakan kembali.
$ docker compose stop dbOutput yang Dihasilkan:
[+] Stopping 1/1
✔ Container toko-produksi-db-1 Stopped 1.2sProbe /healthz mulai gagal sejak detik itu; tiga kegagalan beruntun pada interval 15 detik menuntaskan masa probation, dan daftar stack kini memperlihatkan kejujuran yang kita bayar mahal di seluruh section ini:
$ docker compose psOutput yang Dihasilkan:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
toko-produksi-db-1 postgres:18-alpine "docker-entrypoint.s…" db 6 minutes ago Exited (0)
toko-produksi-cache-1 redis:7-alpine "docker-entrypoint.s…" cache 6 minutes ago Up 6 minutes (healthy)
toko-produksi-backend-1 registry.example.com/toko/backend:1.0.0 "node server.js" backend 6 minutes ago Up 5 minutes (unhealthy)
toko-produksi-proxy-1 nginx:1.29-alpine "nginx -g 'daemon off…" proxy 6 minutes ago Up 5 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcpPerhatikan baris backend: prosesnya tetap Up sementara statusnya unhealthy — dua laporan yang berbeda sumber dan, untuk kasus ini, keduanya benar. Di sinilah recap dari Chapter Container Health harus dibawa terang: Docker tidak memulai ulang container yang unhealthy. Restart policy hanya membaca exit code, healthcheck hanya menulis laporan; container yang sakit akan terus berjalan sampai sembuh sendiri atau sampai sesuatu yang lain menyentuhnya. Skenario kita berakhir bahagia — db dinyalakan kembali dengan docker compose start db, probe berikutnya lulus, dan status pulih sendiri dalam satu interval:
$ docker events --filter event=health_statusOutput yang Dihasilkan (dipotong):
2026-10-16T10:29:04.318405512+07:00 container health_status: healthy e7a13c92f5b0d84a6c1e9372a5d84f6b0c2e8d4a6f1b3c5e7d9a0f2b4c6e8d1a (image=registry.example.com/toko/backend:1.0.0, name=toko-produksi-backend-1)
...
2026-10-16T10:41:47.882104369+07:00 container health_status: unhealthy e7a13c92f5b0d84a6c1e9372a5d84f6b0c2e8d4a6f1b3c5e7d9a0f2b4c6e8d1a (image=registry.example.com/toko/backend:1.0.0, name=toko-produksi-backend-1)
...
2026-10-16T10:44:21.516928207+07:00 container health_status: healthy e7a13c92f5b0d84a6c1e9372a5d84f6b0c2e8d4a6f1b3c5e7d9a0f2b4c6e8d1a (image=registry.example.com/toko/backend:1.0.0, name=toko-produksi-backend-1)Mari kita telaah kronik tersebut dengan anatomi yang kita hafal dari Chapter Docker Events:
- Stempel waktu konsisten zona
+07:00— tiga kejadian berbagi format penuh tanggal, jam berpresisi nanodetik, dan zona WIB; jarak antar baris bisa dihitung langsung: sakit selama dua menit tiga puluh empat detik. - ID penuh 64 karakter heksadesimal — bukan bentuk pendek 12 seperti di
ps; ID yang sama pada ketiga baris menegaskan satu container yang sama mengalami tiga musim. - Atribut
imagedannamemenempel pada setiap baris — kronik terbaca tanpa menyalakan satu pun perintah lain: image mana yang sakit, service mana yang terdampak, kapan mulainya, kapan pulihnya.
Untuk keadaan yang tidak sembuh sendiri, tiga pilihan menutup celah antara laporan dan tindakan. Pilihan pertama adalah tangan manusia: docker compose restart backend saat insiden — sederhana, terukur, dan tepat untuk tim kecil yang menjaga satu-dua stack. Pilihan kedua adalah watchdog pihak ketiga seperti autoheal yang memulai ulang container ber-status unhealthy secara otomatis — berharga untuk jam-jam tanpa orang berjaga, dengan risiko klasiknya sendiri: restart yang menutup gejala sebelum penyebabnya dibaca. Pilihan ketiga adalah naik kelas platform: Swarm dan Kubernetes menyatukan status kesehatan ke dalam keputusan penjadwalan — container yang sakit diganti container baru di tempat lain, bukan sekadar dihidupkan ulang di tempat yang sama — dan itulah peta yang menunggu di Section Orchestration dan Ekosistem.
Stack toko kini tahan guncangan dengan cara yang bisa dijelaskan baris demi baris: probe yang jujur, kebijakan bangkit yang taat, dan kronik yang merekam semua peralihan status. Satu lubang terakhir memisahkan stack ini dari julukan produksi yang sesungguhnya: pintunya masih polos HTTP di port 8080, tanpa gembok, tanpa sertifikat, tanpa janji kepada browser bahwa data pelanggan dienkripsi dalam perjalanan. Chapter Reverse Proxy dan TLS memasang lini depan itu — terminasi TLS di proxy, sertifikat ACME dengan perpanjangan otomatis, dan konfigurasi nginx yang tetap waras ketika IP backend berganti di baliknya.
Referensi #
- Compose File Reference — Services
— definisi
healthcheck,restart,stop_grace_period, dandepends_on. - Start containers automatically — perilaku empat kebijakan restart, jeda backoff, dan kejadian saat daemon start ulang.
- Dockerfile Reference — HEALTHCHECK — nilai bawaan opsi probe dan semantik exit code.
- docker events
— referensi filter
event=health_statusdan format atribut kejadian.