Siklus pengembangan katalog kini berjalan mulus: stack menyala dengan tiga service inti, develop.watch menjaga suntingan kode, dan profil menyimpan peralatan insiden. Pekerjaan sehari-hari kemudian bergeser ke rutinitas yang lebih rendah tapi jauh lebih sering — memeriksa apakah stack sehat, mengikuti log saat menguji satu fitur, masuk ke container untuk melihat isi filesystem, menghentikan satu service tanpa membongkar yang lain, dan membongkar semuanya dengan aman di akhir hari. Compose menyediakan kurang dari dua puluh perintah untuk seluruh kebutuhan itu, dan hampir semuanya sudah kita jumpai sekali-sekali di chapter-chapter sebelumnya. Chapter ini menyusun semuanya menjadi satu peta yang utuh — lengkap dengan output realistisnya, perilaku tepat tiap perintah, dan satu pembedaan yang paling sering menjebak: restart bukanlah cara membaca ulang compose.yaml yang berubah.

Peta Perintah Harian #

Tabel berikut adalah seluruh perintah yang menemani hari-hari Budi mengelola katalog. Kolom contoh memakai nama service web dan db dari stack kita:

PerintahFungsiContoh
upmenyelaraskan daemon dengan compose.yamldocker compose up -d
up --buildmenyelaraskan sambil membangun ulang imagedocker compose up -d --build
psdaftar container project yang berjalandocker compose ps
ps -atermasuk container sekali jalan dari rundocker compose ps -a
logslog gabungan seluruh servicedocker compose logs --tail=20
logs -fmengikuti log satu service secara langsungdocker compose logs -f --tail=20 web
execmenjalankan perintah di container yang hidupdocker compose exec web sh
runcontainer baru sekali jalan dari cetakbiru servicedocker compose run --rm web node --version
restartmenghentikan lalu menyalakan container yang samadocker compose restart web
stopmenghentikan service tanpa membuang containernyadocker compose stop web
startmenyalakan kembali service yang berhentidocker compose start web
downmembongkar container dan network projectdocker compose down
down -vsama, ditambah membuang volume bernamadocker compose down -v
rmmembuang container yang sudah berhentidocker compose rm -f
pullmenarik image seluruh service yang dideklarasikandocker compose pull
buildmembangun image service ber-builddocker compose build
configmerender dan memvalidasi berkasdocker compose config
lsdaftar seluruh project yang berjalan di daemondocker compose ls
watchmengaktifkan aturan develop.watchdocker compose watch

Sebagian besar baris pada tabel itu adalah padanan perintah Docker yang kita kuasai dari Section Dasar Docker CLI — ps menggantikan docker ps, logs menggantikan docker logs, exec menggantikan docker exec — dengan satu perbedaan mendasar: seluruhnya otomatis terbatas pada container milik project, tanpa satu nama container yang perlu diketik panjang. Bagian-bagian berikut membedah perintah-perintah kunci beserta keluaran aslinya.

Membaca Kondisi Stack #

Pagi dimulai dengan satu pertanyaan: apakah semuanya sehat? Jawabannya satu perintah:

bash
$ docker compose ps

Output yang Dihasilkan:

text
NAME             IMAGE               COMMAND                  SERVICE   CREATED         STATUS                    PORTS
katalog-db-1     postgres:18-alpine  "docker-entrypoint.s…"   db        4 minutes ago   Up 4 minutes (healthy)    5432/tcp
katalog-web-1    katalog-web         "node --watch src/ser…"  web       4 minutes ago   Up 4 minutes (healthy)    0.0.0.0:8080->3000/tcp
katalog-proxy-1  nginx:1.29-alpine   "/docker-entrypoint.…"   proxy     4 minutes ago   Up 4 minutes              0.0.0.0:8090->80/tcp

Mari kita telaah kolom-kolom khas Compose pada tabel di atas:

  1. Kolom SERVICE — tanda tangan docker compose ps yang tidak dimiliki docker ps; identitas container di sini adalah cetakbirunya, bukan imagenya, dan kolom inilah yang membuat tiga replika dari satu service tetap mudah dibedakan asalnya.
  2. Kolom NAME mengikuti pola <project>-<service>-<index> — katalog-web-1 adalah instansiasi pertama dari service web pada project katalog, pola yang kita bedah pada Chapter Service.
  3. Kolom STATUS memuat hasil healthcheck — (healthy) pada db dan web adalah lencana dari probe yang kita bangun pada Chapter Healthcheck dan Service Readiness; proxy tampil polos karena memang tidak membawa healthcheck.
  4. Kolom COMMAND menampilkan perintah yang sedang berjalan — node --watch src/server.js pada web berasal dari command yang kita pasang pada Chapter Compose Watch, sementara dua service lain menjalankan entrypoint bawaan imagenya.
  5. Kolom PORTS — pemetaan 0.0.0.0:8080->3000/tcp milik web dan 8090->80 milik proxy; db hanya mencantumkan 5432/tcp sebagai metadata port internal yang tidak diterbitkan ke host.

Dari pandangan yang lebih tinggi, docker compose ls memperlihatkan seluruh project yang sedang hidup di daemon — jawaban untuk pertanyaan “stack apa saja yang menyala di mesin ini?”:

bash
$ docker compose ls

Output yang Dihasilkan:

text
NAME      STATUS        FILES
katalog   running(3)    /home/budi/katalog/compose.yaml

Status running(3) menghitung tiga container hidup di bawah project katalog, dan FILES menunjuk berkas deklarasi yang menjadi sumber kebenarannya — dua kolom yang akan semakin menarik ketika satu mesin menampung lebih dari satu project pada chapter berikutnya.

Mengikuti Log Sebuah Service #

Menguji satu fitur berarti mengawasi satu service. Perintah berikut menempelkan terminal ke log web dengan dua pengaturan sekaligus:

bash
$ docker compose logs -f --tail=20 web

Output yang Dihasilkan:

text
katalog-web-1  | katalog web mendengarkan di port 3000
katalog-web-1  | GET / 200 4ms
katalog-web-1  | GET /healthz 200 1ms
katalog-web-1  | GET /jam 200 0ms
katalog-web-1  | GET / 200 3ms
...

Mari kita telaah tiga perilaku pada keluaran tersebut:

  1. Setiap baris diawali nama container pengirimnya — katalog-web-1 tampil sebagai prefiks karena log gabungan bisa saja berdatangan dari banyak service; pada terminal sungguhan, Compose mewarnai prefiks tiap service dengan warna berbeda sehingga arus log terbaca sekilas pandang.
  2. --tail=20 membatasi riwayat awal — hanya dua puluh baris terakhir yang dicetak sebelum pemantauan dimulai, sehingga log lama tidak membanjiri layar; tanpa flag ini, seluruh riwayat log container ikut tercetak.
  3. -f mengikuti arus secara langsung — terminal menempel pada arus log sampai Ctrl+C ditekan; tanpa -f, perintah mencetak riwayat lalu keluar — pola yang pas untuk membaca “apa yang terjadi tadi” setelah sebuah kejadian.

Perintah tanpa nama service — docker compose logs — menggabungkan log seluruh service sekaligus; itu tampilan yang kita saksikan sejak up interaktif pada Chapter Pengantar Docker Compose.

Masuk ke Container yang Berjalan #

Kadang mata harus melihat isi container sendiri. docker compose exec membuka shell pada container service yang sedang hidup:

bash
$ docker compose exec web sh

Output yang Dihasilkan:

text
/app $ pwd
/app
/app $ ls
node_modules
package.json
src
/app $ exit

Mari kita telaah sesi singkat di atas:

  1. Prompt membuka di /app — direktori kerja bawaan berasal dari WORKDIR pada Dockerfile image, padanan deklaratif flag -w yang kita bedah pada Chapter Working Directory; exec selalu memulai dari titik yang sama dengan proses utama container.
  2. Isi direktori mencerminkan image — package.json, src, dan node_modules hasil RUN npm install; tidak ada berkas host lain karena service web tidak memasang bind mount source.
  3. Sesi berjalan di container yang sama yang melayani permintaan — environment, network, dan identitas proses semuanya milik container hidup itu; perintah printenv DATABASE_URL di sesi yang sama akan menunjukkan nilai dari Chapter Environment dan env File.
  4. exit melepaskan sesi tanpa mematikan container — container tetap melayani permintaan seolah tidak pernah dimasuki.

restart Bukan Membaca Ulang Berkas #

Inilah pembedaan yang paling sering menjebak. Budi menambahkan satu variabel percobaan TEMA: gelap pada blok environment service web, lalu — terbawa kebiasaan — menjalankan restart:

bash
$ docker compose restart web

Output yang Dihasilkan:

text
[+] Restarting 1/1
 ✔ Container katalog-web-1  Restarted
bash
$ docker compose exec web printenv TEMA; echo "exit code: $?"

Output yang Dihasilkan:

text
exit code: 1

Variabel itu tidak ada. Perintah yang benar untuk menerapkan perubahan berkas adalah up:

bash
$ docker compose up -d

Output yang Dihasilkan:

text
[+] Running 3/3
 ✔ Container katalog-db-1     Running
 ✔ Container katalog-web-1    Recreated
 ✔ Container katalog-web-1    Started
bash
$ docker compose exec web printenv TEMA

Output yang Dihasilkan:

text
gelap

Mari kita telaah mengapa keduanya berbeda:

  1. restart menghentikan lalu menyalakan container yang sama — konfigurasi container dibekukan pada saat penciptaannya; kata Restarted berarti proses yang sama berjalan ulang dengan environment yang lama.
  2. exit code: 1 dari printenv — utilitas ini keluar tanpa output ketika variabelnya tidak ditemukan, dan kode keluarnya memberi tahu kegagalannya; mekanisme exit code yang kita bedah pada Chapter Memahami Exit Code bekerja persis sama di dalam container.
  3. up -d membandingkan deklarasi dengan keadaan daemon — perbedaan pada environment membuat container lama tidak lagi cocok, sehingga Compose menciptakan ulang: kata kuncinya Recreated, bukan Restarted.
  4. Bukti fisiknya ada pada kolom CREATED — setelah restart, docker compose ps masih menunjukkan waktu penciptaan yang lama dengan status Up yang baru; setelah up -d, keduanya sama-sama segar.
  5. Aturan praktisnya — restart untuk proses yang menggantung atau perlu dinyalakan ulang tanpa alasan konfigurasi; up -d untuk setiap perubahan compose.yaml. Budi menghapus kembali baris TEMA percobaan itu dan menyelaraskan dengan satu up -d lagi.

Perintah stop dan start adalah pasangan yang lebih lembut: docker compose stop web menghentikan service tanpa membuang containernya, dan docker compose start web menyalakannya kembali dengan konfigurasi yang sama — berguna untuk menghemat sumber daya saat sebuah service tidak dibutuhkan sementara, tanpa kehilangan status apapun yang menempel padanya.

Perintah Sekali Jalan dengan run #

Ada satu kebutuhan yang tidak terjawab exec: menjalankan perintah pada cetakbiru service ketika container utamanya tidak berjalan — atau ketika kita tidak ingin menyentuhnya. docker compose run menciptakan container baru dari konfigurasi service, menjalankan perintah, lalu selesai:

bash
$ docker compose run web node --version

Output yang Dihasilkan:

text
v22.20.0

Container sekali jalan itu tidak tampil pada ps biasa — ia disembunyikan karena memang bukan anggota stack:

bash
$ docker compose ps -a

Output yang Dihasilkan:

text
NAME                    IMAGE               COMMAND                  SERVICE   CREATED         STATUS                    PORTS
katalog-db-1            postgres:18-alpine  "docker-entrypoint.s…"   db        9 minutes ago  Up 9 minutes (healthy)    5432/tcp
katalog-web-1           katalog-web         "node --watch src/ser…"  web       9 minutes ago  Up 9 minutes (healthy)    0.0.0.0:8080->3000/tcp
katalog-proxy-1         nginx:1.29-alpine   "/docker-entrypoint.…"   proxy     9 minutes ago  Up 9 minutes              0.0.0.0:8090->80/tcp
katalog-web-run-8c31f2  katalog-web         "node --version"         web       16 seconds ago Exited (0) 14 seconds ago

Bekas container itu dibersihkan, lalu bentuk yang benar dijalankan:

bash
$ docker compose rm -f

Output yang Dihasilkan:

text
[+] Running 1/1
 ✔ Container katalog-web-run-8c31f2  Removed
bash
$ docker compose run --rm web node --version

Output yang Dihasilkan:

text
v22.20.0

Mari kita telaah perbedaan run dan exec dari keseluruhan alur di atas:

  1. run menciptakan container baru, exec menempel ke container lama — container katalog-web-run-8c31f2 lahir dari cetakbiru service web yang sama, menjalankan node --version, lalu keluar; nama bendanya memuat penanda run sehingga tidak pernah tertukar dengan anggota stack.
  2. Konfigurasi service tetap diterapkan — environment dari env_file dan environment, network project, volume yang dipasang: semuanya hadir pada container sekali jalan ini; satu pengecualian penting: port dari ports tidak diterbitkan, karena dua pemetaan pada satu pintu host akan saling menabrak.
  3. Tanpa --rm, bekasnya tertinggal berstatus Exited — tersembunyi dari ps biasa, hanya tampil pada ps -a, dan dibersihkan lewat docker compose rm -f; flag --rm menghapus bekas itu seketika setelah perintah selesai, dan itulah bentuk yang layak menjadi kebiasaan.
  4. run adalah padanan kerja docker run --rm untuk stack — skrip migrasi, tugas impor data, dan uji cepat versi tooling semuanya pola run --rm; exec tetap satu-satunya jalan untuk bekerja di dalam container yang sedang melayani permintaan.

Perintah top menutup keluarga inspeksi — versi Compose dari docker top yang menampilkan proses di dalam container dari sudut pandang host:

bash
$ docker compose top db

Output yang Dihasilkan (dipotong):

text
katalog-db-1
UID    PID    PPID   C    STIME  TTY  TIME     CMD
70     1      0      0    09:14  ?    00:00:02 postgres
70     124    1      0    09:14  ?    00:00:00 postgres: checkpointer
70     125    1      0    09:14  ?    00:00:00 postgres: background writer
...

Dua hal terbaca seketika dari tabel itu: proses utama postgres berjalan sebagai PID 1 — entrypoint image melepas kendali ke server sungguhan lewat exec, persis pembahasan Chapter CMD dan ENTRYPOINT — dan seluruh proses berjalan di bawah UID 70, user postgres bawaan image Alpine.

Membongkar Stack dengan down #

Akhir hari tiba, dan pembongkarannya satu perintah:

bash
$ docker compose down

Output yang Dihasilkan:

text
[+] Running 7/7
 ✔ Container katalog-proxy-1   Stopped
 ✔ Container katalog-proxy-1   Removed
 ✔ Container katalog-web-1     Stopped
 ✔ Container katalog-web-1     Removed
 ✔ Container katalog-db-1      Stopped
 ✔ Container katalog-db-1      Removed
 ✔ Network katalog_default     Removed

Mari kita telaah urutan dan yang tidak hadir pada keluaran tersebut:

  1. Urutan penghentian adalah kebalikan urutan start — proxy dihentikan lebih dulu, disusul web, terakhir db; Compose membaca benang depends_on dan membongkar dari ujung yang tidak diandalkan service lain, kebalikan persis dari arah gerbang service_healthy pada Chapter depends_on dan Startup Order.
  2. Setiap container melewati Stopped lalu Removed — dua langkah yang sama dengan siklus manual docker stop dan docker rm, dikerjakan berurutan untuk seluruh stack.
  3. Network ikut dibongkar, volume tidak — tidak ada satu baris Volume ... Removed pada keluaran itu; katalog_db-data tetap berdiri, sehingga docker compose up -d besok pagi menemukan kembali seluruh isi tabel produk — janji Chapter Volume dan Bind Mount yang kini terasa sebagai rutinitas.

Perintah-perintah pengelolaan kini lengkap di tangan, dan stack katalog bisa dijalankan, diamati, dimasuki, dan dibongkar dengan nyaman. Namun satu ketegangan belum terurai: compose.yaml kita menumpuk dua kepentingan dalam satu berkas — konfigurasi bersama yang dibaca seluruh tim, dan kebiasaan personal Budi seperti command: node --watch yang tidak relevan bagi produksi, bahkan bisa mengganggunya. Compose memiliki mekanisme berlapis untuk memisahkan keduanya tanpa memecah berkas menjadi banyak salinan yang saling bertengkar: berkas override yang dimuat otomatis, aturan merge yang bisa diprediksi, dan tag kendali untuk kasus-kasus yang menuntut pengecualian. Keseluruhannya adalah materi Chapter Compose Override dan Environment — chapter penutup section ini.

Referensi #