Aplikasi nyata jarang berdiri sendiri: satu website toko butuh web server, database, mungkin cache, dan ketiganya harus saling menemukan lewat jaringan yang tepat dengan urutan hidup yang benar. Menjalankan semuanya manual berarti menghafal beberapa perintah docker run yang panjang, membuat network lebih dulu, lalu menghafal pula urutan mematikannya. Docker Compose memindahkan seluruh kerja itu ke satu berkas YAML: satu perintah membangkitkan semua service, dan satu perintah pula merobohkannya. Karena docker tidak terpasang di lingkungan latihan, kita membedah berkas compose sebagai fixture, mengecek strukturnya dengan awk dan grep, lalu menyimulasikan urutan start yang dihitung dari deklarasi dependensinya.
Masalah Aplikasi Multi-Container #
Chapter Docker
mengajarkan satu container: satu docker run, satu proses utama. Kesulitan muncul saat satu aplikasi membutuhkan tiga service — web, db, cache — yang harus berbagi jaringan, menerima environment variable yang benar, dan dinyalakan berurutan. Tanpa alat bantu, pekerjaan itu menjadi ritual manual:
| Pekerjaan manual | Padanan di compose file |
|---|---|
docker network create toko-net | blok networks: |
docker run --env A --env B --env C sepanjang layar | key environment: |
menghafal urutan docker run | key depends_on: |
docker stop lalu docker rm satu per satu | docker compose down |
Inti pergeserannya: dari perintah imperatif yang dihafal menjadi deklarasi yang disimpan di version control — berkas yang bisa di-code review dan dibaca ulang enam bulan kemudian.
Anatomi docker-compose.yml #
Berkas berikut tersedia di bundle materi ini sebagai docker-compose.yml — aplikasi toko mini dengan dua service:
cat docker-compose.ymlOutput yang Dihasilkan:
services:
web:
image: nginx:1.27
ports:
- "8080:80"
depends_on:
- db
networks:
- internal
db:
image: postgres:17
environment:
POSTGRES_USER: budi
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: toko
volumes:
- data-db:/var/lib/postgresql/data
networks:
- internal
volumes:
data-db:
networks:
internal:Mari kita bedah isi berkas itu:
services:: root key; setiap anak yang menjorok dua spasi di bawahnya adalah satu service — cetak biru container yang dikelola compose.web:denganimage: nginx:1.27: service web dibangun dari image publik; tag dipin ke versi tertentu, bukanlatest, supaya hasilupbulan depan sama dengan hasilnya hari ini.ports: - "8080:80": menerbitkan port — koneksi ke port 8080 di host diteruskan ke port 80 di dalam container (format lengkapnya dibedah di Chapter Container Networking ).depends_on: - db: mendeklarasikan bahwa web membutuhkan db; compose menyalakan db lebih dulu, dan mematikannya paling akhir.environment:padadb:: tiga variabel untuk PostgreSQL; perhatikan${POSTGRES_PASSWORD}yang nilainya diambil dari luar berkas — kredensial tidak dituliskan telanjang di version control.volumes: - data-db:...: data database disimpan di named volumedata-dbsehingga tidak ikut lenyap saat container dihapus; mekanismenya dibedah di Chapter Container Storage .networks: - internal: kedua service bergabung ke jaringan bernamainternal; di dalam jaringan itu, nama service menjadi hostname yang bisa di-resolve.- Root
volumes:dannetworks:: deklarasi sumber daya bersama — compose membuatnya sekali untuk satu proyek, lalu merujuknya dari banyak service.
Membaca Ulang Struktur Compose #
Berkas YAML adalah data, bukan program, jadi kita bisa memeriksanya dengan alat teks biasa. Audit berikut memakai awk sebagai mesin state kecil: status aktif dimulai setelah baris services:, dimatikan begitu bertemu key root lain, lalu setiap nama service yang menjorok dua spasi dicetak:
echo '== service yang didefinisikan =='
awk '/^services:/{dalam=1; next} /^[A-Za-z]/{dalam=0} dalam && /^ [a-zA-Z0-9_-]+:/{print " - " substr($1, 1, length($1)-1)}' docker-compose.yml
echo '== pasangan port yang dipublikasikan =='
grep -E '^ +- "[0-9]+:[0-9]+"' docker-compose.yml | tr -d '"' | sed 's/^ *- //'
echo '== sumber image =='
printf 'image: %d, build: %d\n' "$(grep -c 'image:' docker-compose.yml)" "$(grep -c 'build:' docker-compose.yml)"Output yang Dihasilkan:
== service yang didefinisikan ==
- web
- db
== pasangan port yang dipublikasikan ==
8080:80
== sumber image ==
image: 2, build: 0Tiga pertanyaan terjawab dalam satu blok: aplikasi ini terdiri dari service mana saja (web, db), port mana yang terbuka ke host (8080), dan dari mana image berasal (dua dari image:, nol dari build:). Compose sendiri melakukan pemeriksaan lebih ketat lewat docker compose config — perintah itu membaca berkas lalu mencetak bentuk kanoniknya, cara tercepat memastikan YAML tidak salah indentasi sebelum benar-benar up.
depends_on dan Urutan Hidup #
depends_on mengubah compose file menjadi graf berarah: web menunjuk db berarti db harus hidup sebelum web, dan compose membalik urutan itu saat down. Untuk melihat algoritmanya bekerja tanpa docker, kita modelkan dependensi tiga service dalam array asosiatif bash, lalu jalankan pengurutan topologis sederhana:
# kamus dependensi: service -> daftar service yang dibutuhkannya
declare -A deps
deps[web]="db cache"
deps[db]=""
deps[cache]=""
mulai=""
tersisa="db cache web"
while [ -n "$tersisa" ]; do
maju=0
for svc in $tersisa; do
siap=1
for d in ${deps[$svc]}; do
case " $mulai " in *" $d "*) ;; *) siap=0 ;; esac
done
if [ "$siap" = 1 ]; then
echo "start: $svc"
mulai="$mulai $svc"
maju=1
fi
done
baru=""
for svc in $tersisa; do
case " $mulai " in *" $svc "*) ;; *) baru="$baru $svc" ;; esac
done
tersisa="${baru# }"
if [ "$maju" = 0 ] && [ -n "$tersisa" ]; then
echo "terhenti: dependensi melingkar pada $tersisa"
break
fi
done
echo 'urutan stop (kebalikan start):'
printf '%s\n' $mulai | tacOutput yang Dihasilkan:
start: db
start: cache
start: web
urutan stop (kebalikan start):
web
cache
dbMari kita bedah skripnya:
declare -A deps: array asosiatif sebagai graf; web membutuhkan dua service sekaligus, db dan cache tidak membutuhkan apa pun.while [ -n "$tersisa" ]: satu putaran menyalakan semua service yang siap, putaran diulang sampai daftar sisa kosong.case " $mulai " in *" $d "*): tes keanggotaan dengan spasi pengaman di kedua sisi supaya namadbtidak ikut cocok dengan namadb2.- Blok
baru: membangun ulang daftar sisa — service yang baru dinyalakan dikeluarkan, sisanya menunggu putaran berikutnya. - Pemeriksaan
maju = 0: bila satu putaran tidak berhasil menyalakan apa pun padahal masih ada sisa, dependensinya melingkar dan tidak ada urutan start yang valid.
Perintah Sehari-hari #
Empat perintah menutup satu siklus hidup lengkap, semuanya dijalankan dari direktori yang memuat compose file:
| Perintah | Efek |
|---|---|
docker compose up -d | membuat network dan volume, lalu menyalakan semua service di background |
docker compose ps | status seluruh service proyek ini |
docker compose logs -f web | mengikuti log satu service; Ctrl+C keluar tanpa mematikan apa pun |
docker compose down | menghentikan dan menghapus container beserta network — volume selamat |
Berikut contoh tampilan docker compose up -d pada berkas di atas, dijalankan dari folder bernama toko:
[+] Running 4/4
✔ Network toko_internal Created
✔ Volume "toko_data-db" Created
✔ Container toko-db-1 Started
✔ Container toko-web-1 StartedNama-nama itu bukan kebetulan: compose mengawali seluruh sumber daya dengan nama proyek — defaultnya nama folder — sehingga dua proyek berbeda di satu mesin tidak saling menabrak. Untuk menambah kapasitas, docker compose up -d --scale web=3 menyalakan tiga container web di belakang nama service yang sama.
Override, .env, dan Profile #
Satu berkas compose sering perlu dua wajah: versi produksi yang ramping dan versi pengembangan yang menempelkan source code lewat bind mount. Compose menjawabnya dengan override file: bila docker-compose.override.yml ada di direktori yang sama, kedua berkas digabung otomatis dan nilai dari override menang. Simpan versi bersih di docker-compose.yml, variasi lokal di override yang masuk .gitignore; untuk kontrol eksplisit, docker compose -f berkas1 -f berkas2 up membaca keduanya berurutan.
Kredensial menempati jalur terpisah: berkas .env di direktori proyek dibaca otomatis, dan setiap ${VARIABEL} di compose file diganti nilainya. Formatnya sengaja sederhana — KEY=value satu per baris — sehingga bisa dibaca pula oleh shell biasa:
printf 'POSTGRES_PASSWORD=CONTOH_PASSWORD_KUAT\nPOSTGRES_DB=toko\n' > .env
set -a
. ./.env
set +a
grep 'POSTGRES_PASSWORD' docker-compose.yml
printf 'nilai yang disuntikkan: %s\n' "$POSTGRES_PASSWORD"Output yang Dihasilkan:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
nilai yang disuntikkan: CONTOH_PASSWORD_KUATEmpat baris pertama mensimulasikan yang dilakukan compose: membaca .env, lalu menyuntikkan tiap variabel. Baris grep memperlihatkan tempat suntikan terjadi — placeholder di dalam berkas — dan baris terakhir nilainya yang telah terisi.
Terakhir, profiles: service yang diberi profiles: ["debug"] dilewati oleh up biasa dan hanya ikut dinyalakan bila --profile debug disebut — cara menjinakkan alat bantu seperti adminer atau profiler di dalam berkas yang sama tanpa membuatnya ikut produksi.
Dengan compose, satu aplikasi multi-container menjadi satu berkas yang bisa dibaca, dibedah, dan diverifikasi — persis yang baru saja kita lakukan. Tetapi mesin yang menjalankannya masih bergaya lama: satu daemon yang berjalan sebagai root. Chapter berikutnya memperkenalkan penantangnya yang membuang daemon sekaligus root sejak baris pertama: Podman .
Referensi lanjut: dokumentasi Docker Compose untuk perilaku tiap perintah, serta Compose file reference untuk daftar lengkap key yang sah.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
docker-compose.yml
Unduh berkasservices:
web:
image: nginx:1.27
ports:
- "8080:80"
depends_on:
- db
networks:
- internal
db:
image: postgres:17
environment:
POSTGRES_USER: budi
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: toko
volumes:
- data-db:/var/lib/postgresql/data
networks:
- internal
volumes:
data-db:
networks:
internal: