Service web kini tahu persis cara mencapai db lewat DATABASE_URL, dan di mesin Budi stack berjalan mulus hari demi hari. Ceritanya berubah begitu Siti ikut serta: ia menyalin repositori katalog ke mesinnya yang masih segar, menyalin .env.example menjadi .env sesuai pola chapter sebelumnya, lalu menjalankan docker compose up — dan web mati beberapa detik kemudian dengan galat koneksi yang tidak pernah Budi lihat. Inilah masalah klasik multi-service: container db memang dinyalakan, tetapi db yang menyala belum tentu db yang siap, karena inisialisasi PostgreSQL memakan waktu beberapa detik pada volume yang masih segar. Chapter ini membedah cara Compose mengatur urutan start lewat kunci depends_on — mulai dari bentuk daftarnya yang sederhana, bentuk petanya yang bisa menunggu kesehatan, sampai batas paling pentingnya: urutan start tetap bukan jaminan kesiapan aplikasi.
Galat yang Datang dan Pergi #
Mari kita lihat dulu tampang masalahnya di mesin Siti. Stack menyala tanpa keluhan:
$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Created
✔ Container katalog-db-1 Started
✔ Container katalog-web-1 Started
✔ Container katalog-web-1 ExitedBaris terakhir itu kabar duka yang baru muncul belasan detik kemudian. Keadaan stack terbaca jelas lewat docker compose ps:
$ docker compose psOutput yang Dihasilkan:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
katalog-db-1 postgres:18-alpine "docker-entrypoint.s…" db 58 seconds ago Up 57 seconds 5432/tcp
katalog-web-1 katalog-web "docker-entrypoint.s…" web 58 seconds ago Exited (1) 52 seconds agoLog container web memperlihatkan biang keroknya:
$ docker compose logs webOutput yang Dihasilkan (dipotong):
katalog-web-1 | node:internal/process/promises:396
katalog-web-1 | triggerUncaughtException(err, true /* fromPromise */);
katalog-web-1 | ^
katalog-web-1 |
katalog-web-1 | Error: connect ECONNREFUSED 172.18.0.2:5432
katalog-web-1 | at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1634:16)
katalog-web-1 | ...
katalog-web-1 | errno: -111, code: 'ECONNREFUSED', syscall: 'connect',
katalog-web-1 | address: '172.18.0.2', port: 5432
katalog-web-1 |
katalog-web-1 | Node.js v22.20.0Mari kita telaah rangkaian kejadian di atas:
ECONNREFUSED— sistem operasi menolak koneksi karena tidak ada satu proses pun yang sedang mendengarkan di172.18.0.2:5432; alamat itu adalah containerdbdi dalam networkkatalog_default, tepat sasaran dariDATABASE_URL.- Container
dbmemangUp— proses utamanya berjalan, tetapi volumenya masih segar sehingga entry point PostgreSQL sibuk menjalankaninitdb; server database belum membuka socket untuk menerima koneksi siapa pun. webkeluar dengan exit code 1 — Node.js tidak menemukan handler untuk galat koneksi, prosesnya berhenti, dan tanpa restart policy container itu tinggal statusExited (1)selamanya, mekanisme exit code yang kita bedah pada Chapter Memahami Exit Code.- Di mesin Budi galat ini tak pernah tampak — volume
katalog_db-datayang sudah terisi membuatinitdbdilewati, sehinggadbsiap hampir seketika; hasil yang bergantung kecepatan mesin dan umur volume seperti inilah yang disebut race condition, dan justru sifatnya yang kadang-hilang-kadang-tidak membuatnya sulit diburu.
depends_on Bentuk Daftar: Urutan Start Saja #
Kunci depends_on adalah cara service menyatakan bahwa dirinya butuh service lain lebih dulu. Bentuk paling sederhana berupa daftar nama service:
web:
build: .
ports:
- "8080:3000"
env_file:
- .env
environment:
DATABASE_URL: postgres://katalog:${POSTGRES_PASSWORD}@db:5432/katalog
depends_on:
- dbDengan deklarasi ini Compose menjamin satu hal: container db dinyalakan lebih dulu, dan container web baru diciptakan setelah status container db mencapai Started. Sayangnya Started hanya berarti proses utama sudah berjalan — tidak ada janji apa pun bahwa PostgreSQL sudah mendengarkan port 5432. Pada mesin Siti, up kedua tampak berhasil karena db kebetulan sudah lama siap; tetapi ketika mereka mengulang siklus down dan up beberapa kali, pada putaran ketiga web kembali mati dengan ECONNREFUSED yang sama. Urutan start yang benar ternyata masih menyisakan jendela race condition di dalamnya.
depends_on Bentuk Peta dengan condition #
Bentuk kedua depends_on memperbolehkan kita menuntut lebih banyak dari sekadar urutan. Service dependensi ditulis sebagai kunci, dan kondisinya dinyatakan gamblang:
depends_on:
db:
condition: service_healthyTiga kondisi yang tersedia membentuk tangga ketegasan:
service_started— perilaku yang sama dengan bentuk daftar: tunggu container dependensi berjalan; ini nilai bawaan bilaconditiontidak ditulis.service_healthy— tunggu container dependensi berstatushealthy, status yang hanya bisa dicapai lewat healthcheck; inilah kondisi yang kita pakai untukdb.service_completed_successfully— tunggu service dependensi selesai dan keluar dengan exit code 0; kondisi ini lahir untuk service one-shot seperti job migrasi database yang harus tuntas sebelum aplikasi menyala — pola lengkapnya kita praktikkan pada Chapter Startup dan Dependency di Section Multi-Container Application.
healthcheck untuk Service db #
Kondisi service_healthy hanya bermakna bila db punya healthcheck — tanpa probe, status healthy tidak akan pernah tercapai dan web menunggu selamanya. Compose menempelkan healthcheck langsung pada service, tanpa menyentuh Dockerfile image:
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: katalog
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: katalog
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U katalog -d katalog"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
start_interval: 2sMari kita telaah enam kunci blok healthcheck di atas:
test— perintah probe yang dijalankan di dalam container;pg_isreadyadalah utilitas resmi PostgreSQL yang keluar dengan exit code 0 hanya bila server benar-benar menerima koneksi, danCMD-SHELLmenjalankannya melalui shell container.interval: 5s— jeda antara dua pemeriksaan; angka kecil membuat status berubah responsif.timeout: 3s— batas waktu tiap probe; probe yang menggantung lebih lama dari ini dianggap gagal.retries: 5— jumlah kegagalan beruntun sebelum status container berubah menjadiunhealthy.start_period: 10s— masa hangar di awal; kegagalan probe selama periode ini tidak dihitung keretries, memberi ruang bagiinitdbyang kadang lambat.start_interval: 2s— jeda khusus yang hanya berlaku selama masa hangar, agar statushealthytercapai lebih cepat begitu server siap.
Desain probe yang benar — menguji fungsi layanan, bukan sekadar keberadaan prosesnya — punya babnya sendiri di Chapter Healthcheck dan Service Readiness.
up dengan Urutan yang Sehat #
Budi mendorong perbaikan ini ke repositori, Siti menariknya, lalu menjalankan siklus penuh dari nol:
$ docker compose down
$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Created
✔ Container katalog-db-1 Healthy
✔ Container katalog-web-1 StartedKata pada baris db berubah dari Started menjadi Healthy — dan perubahan satu kata itu adalah seluruh isi janjinya. Mari kita telaah alur keputusan yang barusan terjadi:
Diagram di atas memetakan tanggung jawab pembagian kerja antara Compose dan daemon. Mari kita telaah alurnya:
dbdinyalakan lebih dulu — Compose menjalankan service dependensi sebelum service yang bergantung, persis seperti bentuk daftar.- Probe berjalan berulang — selama
pg_isreadybelum lulus, containerdbberstatushealth: startingdan Compose menahan diri untuk tidak melanjutkan. - Gerbang
healthyterbuka — begitu probe lulus, Compose baru menciptakan containerweb; pada mesin Siti,webkini bertahan hidup melewati detik-detik awal yang dulu mematikannya. - Keadaan akhir terlihat di
ps—docker compose psmenampilkandbdengan statusUp ... (healthy)danwebdenganUp, tanpa satu punExited.
restart on-failure: Lapisan Ketahanan #
Gerbang service_healthy menjaga momen start, tetapi hubungan web dan db tidak berakhir di situ. Di tengah jalan db bisa saja dibuat ulang, jaringan bisa tersendat sesaat, dan koneksi yang sudah terbentuk bisa terputus — web akan keluar dengan exit code 1 sama seperti sebelumnya. Kunci restart menambah lapisan ketahanan untuk kejadian semacam itu:
restart: on-failureKebijakan ini milik keluarga yang kita bedah pada Chapter Restart Policy: no sebagai nilai bawaan, on-failure[:maks], always, dan unless-stopped. Daemon merespons exit code — bukan urusan kesiapan — dengan menyalakan ulang container yang gagal, dan jeda antar percobaan dimulai dari 100 milidetik lalu berlipat ganda.
Tiga tingkat pengaturan urutan start kini bisa dirangkum dalam satu tabel:
| Tingkat | Deklarasi depends_on | Yang ditunggu sebelum web menyala | Kecocokan |
|---|---|---|---|
| 1 | tidak ada | Tidak ada — Compose bebas memulai paralel | Service yang saling tidak bergantung |
| 2 | daftar, atau condition: service_started | Container db berjalan — prosesnya hidup | Service yang kesiapannya hampir seketika |
| 3 | condition: service_healthy | Container db berstatus healthy | Database dan service berat awal |
Urutan start kini berada dalam kendali kita: web tidak lagi berlari sebelum db sanggup menerima koneksi, dan kegagalan yang lolos pun ditangkap restart policy. Namun seluruh bangunan ini bertumpu pada satu benda yang belum pernah kita periksa kelayakannya: probe pg_isready itu sendiri. Healthcheck yang salah rancang membuat gerbang service_healthy bohong besar — meloloskan service yang sakit, atau menggantungkan stack pada service yang sehat. Cara mendesain probe yang jujur, membaca siklus statusnya, dan menyusun berkas katalog final — materi Chapter Healthcheck dan Service Readiness.
Referensi #
- Control startup and shutdown order in Compose
— pola
depends_ondenganconditiondan peringatan resminya soal kesiapan aplikasi. - Compose File Reference — Services
— definisi
depends_on,healthcheck, danrestart. - docker compose up — CLI Reference
— perilaku
upterhadap urutan dan status container. - PostgreSQL
— image resmi postgres beserta utilitas
pg_isreadyuntuk probe kesiapan.