Docker Compose: Menyusun Stack Multi-Service
Begitu sebuah aplikasi web lepas dari tahap prototipe, ia jarang berdiri sendiri. Ada database untuk data persisten, ada cache untuk mempercepat query yang berulang, ada backend API yang terpisah dari frontend, mungkin juga worker untuk queue. Menjalankan semua itu manual satu per satu di terminal cepat jadi menyiksa, apalagi kalau harus diulang tiap kali onboarding developer baru atau pindah server. Di sinilah Docker Compose berguna: satu file docker-compose.yml mendeskripsikan seluruh stack, dan satu perintah menghidupkannya.
Memisahkan service berdasarkan tanggung jawab
Prinsip dasar yang saya pegang saat menulis compose file: satu service, satu tanggung jawab. Jangan menumpuk database dan aplikasi dalam satu container hanya karena terasa lebih ringkas. Selain menyulitkan scaling dan debugging, ini juga bikin log jadi campur aduk. Struktur umum yang biasa saya pakai untuk aplikasi backend dengan database dan cache kurang lebih begini:
services:
app:
build: .
depends_on:
- db
- cache
environment:
DB_HOST: db
CACHE_HOST: cache
networks:
- internal
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
networks:
- internal
cache:
image: redis:7
networks:
- internal
volumes:
db_data:
networks:
internal:
Perhatikan bahwa nama service (db, cache) otomatis jadi hostname yang bisa dipakai antar container lewat DNS internal Compose. Tidak perlu hardcode IP, dan ini salah satu alasan kenapa Compose jauh lebih nyaman dibanding menjalankan container satu-satu secara manual.
Volume untuk data, bukan untuk kode
Satu kesalahan yang cukup sering saya lihat (dan pernah saya lakukan sendiri) adalah menganggap container itu tempat menyimpan data secara permanen. Container itu sifatnya sekali pakai — begitu di-recreate, isi filesystem-nya hilang kecuali di-mount sebagai volume. Untuk database, cache dengan persistence, atau file upload, selalu pakai volumes eksplisit seperti contoh di atas. Untuk kode aplikasi sendiri saat development, bind mount ke source code di host juga lazim dipakai supaya perubahan langsung terlihat tanpa rebuild image, misalnya ./src:/app/src.
depends_on tidak menjamin service benar-benar siap
Ini poin yang sering bikin bingung developer yang baru pindah dari docker run manual ke Compose. depends_on hanya mengatur urutan container dinyalakan, bukan menunggu sampai database benar-benar siap menerima koneksi. Postgres, misalnya, butuh beberapa detik setelah container jalan sebelum port-nya benar-benar bisa dipakai. Kalau aplikasi langsung connect saat startup, bisa gagal dengan error koneksi yang membingungkan. Solusi yang lebih andal adalah pakai healthcheck pada service database, lalu tambahkan condition: service_healthy di depends_on sisi aplikasi, atau — kalau frameworknya mendukung — buat retry logic sederhana di level aplikasi saat startup.
Environment dan konfigurasi per lingkungan
Sebagai ilustrasi, kalau stack yang sama harus jalan berbeda di development dan production (misalnya volume bind mount hanya perlu di development), pola yang umum dipakai adalah base docker-compose.yml plus file override seperti docker-compose.override.yml untuk development atau docker-compose.prod.yml untuk production, dijalankan dengan -f berlapis. Untuk rahasia seperti password database, jangan ditulis langsung di compose file — pakai file .env yang di-gitignore, atau secret manager kalau infrastrukturnya sudah lebih matang.
Takeaway
Compose file yang baik itu mudah dibaca ulang enam bulan kemudian: satu service satu tanggung jawab, volume eksplisit untuk data, dependency startup yang benar-benar ditunggu kesiapannya, dan rahasia yang tidak menempel di kode. Dengan pola ini, stack multi-service jadi sesuatu yang bisa dinyalakan siapa saja di tim hanya dengan satu perintah, bukan ritual yang cuma dihafal satu-dua orang.