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 manualPadanan di compose file
docker network create toko-netblok networks:
docker run --env A --env B --env C sepanjang layarkey environment:
menghafal urutan docker runkey depends_on:
docker stop lalu docker rm satu per satudocker 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:

bash
cat docker-compose.yml

Output yang Dihasilkan:

text
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:

  1. services:: root key; setiap anak yang menjorok dua spasi di bawahnya adalah satu service — cetak biru container yang dikelola compose.
  2. web: dengan image: nginx:1.27: service web dibangun dari image publik; tag dipin ke versi tertentu, bukan latest, supaya hasil up bulan depan sama dengan hasilnya hari ini.
  3. 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 ).
  4. depends_on: - db: mendeklarasikan bahwa web membutuhkan db; compose menyalakan db lebih dulu, dan mematikannya paling akhir.
  5. environment: pada db:: tiga variabel untuk PostgreSQL; perhatikan ${POSTGRES_PASSWORD} yang nilainya diambil dari luar berkas — kredensial tidak dituliskan telanjang di version control.
  6. volumes: - data-db:...: data database disimpan di named volume data-db sehingga tidak ikut lenyap saat container dihapus; mekanismenya dibedah di Chapter Container Storage .
  7. networks: - internal: kedua service bergabung ke jaringan bernama internal; di dalam jaringan itu, nama service menjadi hostname yang bisa di-resolve.
  8. Root volumes: dan networks:: 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:

bash
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:

text
== service yang didefinisikan ==
  - web
  - db
== pasangan port yang dipublikasikan ==
8080:80
== sumber image ==
image: 2, build: 0

Tiga 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:

bash
# 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 | tac

Output yang Dihasilkan:

text
start: db
start: cache
start: web
urutan stop (kebalikan start):
web
cache
db

Mari kita bedah skripnya:

  1. declare -A deps: array asosiatif sebagai graf; web membutuhkan dua service sekaligus, db dan cache tidak membutuhkan apa pun.
  2. while [ -n "$tersisa" ]: satu putaran menyalakan semua service yang siap, putaran diulang sampai daftar sisa kosong.
  3. case " $mulai " in *" $d "*): tes keanggotaan dengan spasi pengaman di kedua sisi supaya nama db tidak ikut cocok dengan nama db2.
  4. Blok baru: membangun ulang daftar sisa — service yang baru dinyalakan dikeluarkan, sisanya menunggu putaran berikutnya.
  5. 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:

PerintahEfek
docker compose up -dmembuat network dan volume, lalu menyalakan semua service di background
docker compose psstatus seluruh service proyek ini
docker compose logs -f webmengikuti log satu service; Ctrl+C keluar tanpa mematikan apa pun
docker compose downmenghentikan dan menghapus container beserta network — volume selamat

Berikut contoh tampilan docker compose up -d pada berkas di atas, dijalankan dari folder bernama toko:

text
[+] Running 4/4
 ✔ Network toko_internal   Created
 ✔ Volume "toko_data-db"   Created
 ✔ Container toko-db-1     Started
 ✔ Container toko-web-1    Started

Nama-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:

bash
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:

text
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
nilai yang disuntikkan: CONTOH_PASSWORD_KUAT

Empat 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 berkas
yaml
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: