Parametrisasi pada chapter sebelumnya membuat satu compose.yaml mampu melayani banyak mesin, tetapi ritual harian pengembangan masih menuntut satu langkah yang mahal: setiap suntingan pada server.js harus diikuti docker compose up -d --build, karena up memang tidak pernah membangun ulang image yang sudah ada — jebakan yang kita buktikan pada Chapter Image, Build, dan Pull. Build lengkap itu hanya memakan delapan hingga sembilan detik berkat cache layer, namun siklus edit-build-uji yang diulang ratusan kali per hari menjadikannya pemborosan yang nyata. Alternatif bind mount yang kita kenal dari Chapter Volume dan Bind Mount menyelesaikan separuh masalah — kode langsung terlihat di container — tetapi membuka pertanyaan baru tentang dependensi dan perilaku dua arah yang tidak selalu kita inginkan. Compose menyediakan jalan ketiga yang lebih presisi: blok develop.watch, yang memantau berkas di host dan bereaksi dengan salah satu dari beberapa aksi — menyalin berkas yang berubah, membangun ulang image, atau me-restart container — sesuai aturan yang kita tulis sendiri di dalam compose.yaml.
Ritual Rebuild yang Mahal #
Mari kita ukur dulu nyerinya dengan teliti. Setiap suntingan satu baris pada server.js menempuh perjalanan yang sama: Dockerfile dibaca ulang, COPY server.js ./ membatalkan cache-nya, image baru diekspor, container dibuat ulang, aplikasi menyala kembali. Layer RUN npm install memang tetap ter-cache karena package.json tidak berubah — strategi urutan COPY dari Chapter Build Cache sudah menjamin itu — tetapi sisa perjalanannya tetap harus dibayar penuh pada setiap iterasi. Bind mount ./src:/app/src memangkas perjalanan itu menjadi nol detik: suntingan di host langsung tampil di path container karena kernel yang mem-mount-nya. Namun bind mount bekerja dengan cara menutup path container dengan isi host, sehingga keputusan tentang apa yang boleh tertimpa menjadi keputusan permanen, dan perubahan package.json tetap menuntut rebuild manual karena node_modules di dalam image tidak pernah tersentuh jalur ini. Yang kita butuhkan bukan salah satu ekstrem, melainkan peraturan per kelas berkas: kode disalin begitu berubah, manifest dependensi memicu rebuild, dan sisanya dibiarkan.
Refactor Kecil ke Folder src #
Sebelum aturan itu ditulis, Budi merapikan struktur proyek yang mulai membesar: server.js pindah ke folder src/ agar rapi ketika berkas-berkas lain menyusul. Struktur folder katalog kini berbentuk:
katalog/
├── compose.yaml
├── Dockerfile
├── package.json
├── nginx.conf
├── db_password.txt
├── .env
└── src/
└── server.jsDockerfile menyesuaikan dua barisnya:
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY src/ ./src/
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]Mari kita telaah dua baris yang berubah: COPY src/ ./src/ menyalin seluruh folder sumber sekaligus, sehingga berkas baru di bawah src/ otomatis ikut pada build berikutnya tanpa menyunting Dockerfile; dan CMD ["node", "src/server.js"] mengikuti lokasi barunya. Urutan COPY package.json sebelum COPY src/ sengaja dipertahankan agar RUN npm install tetap ter-cache selama manifest tidak berubah — persis disiplin Chapter Build Cache.
Blok develop dan Jenis Aksinya #
Aturan pemantauan hidup di bawah kunci develop pada service, dan bentuknya adalah daftar aturan watch — setiap aturan memasangkan satu path di host dengan satu action. Service web kini membawa blok berikut:
web:
build: .
ports:
- "8080:3000"
command: node --watch src/server.js
develop:
watch:
- path: ./src
action: sync
target: /app/src
ignore:
- node_modules/
- path: ./package.json
action: rebuild
- path: ./Dockerfile
action: rebuildMari kita telaah kunci demi kunci blok di atas:
develop:di samping kunci lain service — pemantauan adalah urusan service, bukan project; proxy dan db boleh tidak punya blok ini sama sekali.path: ./src— folder yang diawasi di host, dihitung relatif terhadap lokasi compose.yaml; Compose memantau folder itu beserta seluruh subdirektorinya secara rekursif.action: sync— ketika berkas di bawah./srcberubah, Compose menyalin berkas itu ke path container yang ditujutarget: /app/src— container tetap yang sama, image tetap yang sama, tidak ada rebuild.ignore: node_modules/— pola yang dikecualikan dari pemantauan; foldernode_modulesdi host — misalnya muncul karena tooling editor — tidak pernah tersalin ke container, karena dependensi container sudah terpasang dariRUN npm installdi dalam image.action: rebuilduntuk./package.json— perubahan manifest dependensi tidak bisa diselesaikan salin-menyalin; Compose menjalankan build ulang penuh lalu membuat ulang container dari image hasilnya.action: rebuilduntuk./Dockerfile— alasan yang sama berlaku untuk resep build: suntingan pada Dockerfile hanya bermakna setelah di-build ulang.command: node --watch src/server.js— menimpa CMD image dengan mode pantau bawaan Node.js 22: begitu berkas kode berubah, proses Node memulai ulang dirinya sendiri. Kombinasisyncdari Compose dan--watchdari aplikasi inilah yang membuat suntingan kode hidup tanpa rebuild.
Tiga jenis aksi yang tersedia, beserta varian kombinasinya, dirangkum pada tabel berikut:
| Aksi | Yang dilakukan Compose | Cocok untuk |
|---|---|---|
sync | menyalin berkas yang berubah ke target di container | kode sumber saat aplikasi punya reload sendiri |
rebuild | membangun ulang image lalu membuat ulang container | package.json, Dockerfile, berkas build |
restart | me-restart container tanpa rebuild | berkas yang dibaca entry point saat start |
sync+restart | menyalin berkas lalu me-restart container | kode tanpa reload server, dengan image tetap |
Aksi restart paling natural ketika kode sudah hadir di container lewat bind mount: suntingan pada berkas yang dibaca proses saat startup — skrip entry point, berkas konfigurasi yang diurai sekali — hanya berarti setelah proses dijalankan ulang dari awal. Varian sync+restart menggabungkan kedua langkah itu untuk aplikasi yang tidak memiliki reload server sendiri.
Menjalankan docker compose watch #
Budi menerapkan seluruh perubahan dengan satu rebuild terakhir yang manual, lalu menyalakan pemantauan pada terminal kedua:
$ docker compose up -d --buildOutput yang Dihasilkan (dipotong):
[+] Building 8.9s (14/14) FINISHED
...
=> [4/7] RUN npm install 0.0s (cached)
=> [5/7] COPY src/ ./src/ 0.1s
...
[+] Running 3/3
✔ Container katalog-db-1 Running
✔ Container katalog-web-1 Recreated
✔ Container katalog-web-1 Started$ docker compose watchOutput yang Dihasilkan:
watching /home/budi/katalog/compose.yaml and subdirectoriesMari kita telaah dua keluaran di atas: build berjalan dengan npm install ter-cache — manifest memang tidak berubah — sementara COPY src/ menjadi layer baru; container web dibuat ulang karena command dan develop barunya mengubah deklarasi service; dan docker compose watch kemudian duduk di terminal sebagai proses latar depan yang terus berjalan — menekan Ctrl+C hanya menghentikan pemantauan, sedangkan stack tetap menyala di terminal pertama.
Suntingan Kode yang Tersalin Tanpa Rebuild #
Saatnya membuktikan janji utamanya. Budi menambahkan satu rute kecil di ujung src/server.js — endpoint yang menjawab jam server:
$ echo 'app.get("/jam", (_req, res) => res.send(new Date().toISOString()));' >> src/server.jsTerminal pemantauan merespons seketika:
Syncing web
Synced web$ curl localhost:8080/jamOutput yang Dihasilkan:
2026-10-14T08:41:07.482ZRute baru itu hidup pada detik yang sama. Mari kita telaah rantai peristiwa di baliknya:
- Compose mendeteksi perubahan pada
./src/server.js— aturansyncberlaku, berkas disalin ke/app/src/server.jsdi dalam container yang sedang berjalan; tidak ada satu baris build pada terminal, tidak adaRecreated. - Proses Node memulai ulang dirinya sendiri —
node --watchmelihat berkas berubah dan me-restart aplikasi; ini adalah reload di tingkat proses, bukan di tingkat container. curlmembuktikan hasil akhirnya — endpoint/jammenjawab dengan stempel waktu server, sesuatu yang mustahil ada pada proses lama.- Kombinasi dua pemantau inilah rahasianya — Compose mengurus perpindahan berkas, aplikasi mengurus pemuatan ulang; sync tanpa reload server hanya akan menyalin berkas ke container yang tidak pernah membacanya ulang.
- Biaya per iterasi jatuh drastis — tidak ada build, tidak ada pembuatan container; suntingan berikutnya cukup diulang dari editor.
Suntingan yang Menuntut Rebuild #
Kelas perubahan kedua menempuh jalur yang berbeda. Budi membuka package.json, menambahkan satu dependensi kecil pada blok dependencies, lalu menyimpan berkasnya. Terminal pemantauan kini mencetak jalur rebuild:
Rebuilding web
[+] Building 7.4s (14/14) FINISHED
=> [4/7] RUN npm install 6.3s
...
=> => naming to docker.io/library/katalog-web
Rebuilt web
[+] Running 2/2
✔ Container katalog-web-1 Recreated
✔ Container katalog-web-1 StartedMari kita telaah jalur rebuild tersebut:
- Aturan
rebuildterpicu olehpackage.json— salin-menyalin satu berkas manifest tidak pernah memasang dependensi barunya; satu-satunya jalan benar adalah menjalankan ulangRUN npm installdi dalam build. npm installkehilangan cache-nya — 6.3 detik — perubahan manifest membatalkan layer dependensi, persis perilaku cache yang kita bedah pada Chapter *Build Cache`; inilah harga yang memang harus dibayar, dan watch memastikannya hanya dibayar ketika memang perlu.- Container dibuat ulang dari image baru — proses
RecreatedlaluStartedterjadi otomatis tanpa satu perintah tambahan, dannode --watchkembali aktif dengancommandyang sama. - Dua kelas berkas, dua jalur, nol perintah manual — aturan yang sama juga menjaga
Dockerfile: suntingan pada resep build memicu jalur rebuild yang identik.
Sync atau Bind Mount #
Bagian penutup ini menjawab pertanyaan yang pasti muncul: kalau bind mount juga bisa menghadirkan kode di container, kapan memilih yang mana? Keduanya memecahkan masalah yang sama dengan karakter yang berbeda:
| Aspek | sync pada develop.watch | Bind mount source |
|---|---|---|
| Yang berpindah | hanya berkas yang berubah | seluruh path tertutup isi host |
node_modules | tetap milik image — hasil npm install | host harus menyediakan isinya sendiri |
| Arah | satu arah, host ke container | dua arah |
Reaksi terhadap package.json | bisa diatur — aturan rebuild terpisah | tidak ada — tetap rebuild manual |
Aturan praktisnya sederhana. Pilih sync ketika container seharusnya mempertahankan isi imagenya — dependensi hasil build tetap utuh, host tidak perlu menginstall apa pun, dan yang berpindah hanya suntingan kode. Pilih bind mount ketika dua arah memang dibutuhkan — misalnya berkas yang ditulis aplikasi ingin dibaca tooling di host — dan siap menanggung konsekuensi path yang tertutup. Pada keduanya, develop.watch tetap menjadi pengawas kelas berkas yang menuntut reaksi: rebuild untuk manifest dan resep, restart untuk konfigurasi.
Stack katalog kini nyaman dipakai hari demi hari: suntingan kode hidup seketika, dependensi terpasang ulang otomatis, dan seluruh aturannya tertulis sebagai bagian compose.yaml yang bisa di-commit. Namun semakin nyaman ritual ini, semakin sering pula kita berhadapan dengan perintah-perintah pengelolaan yang menyertainya — memeriksa kondisi stack, mengikuti log, masuk ke container, membedakan restart dari recreate, dan membongkar dengan aman. Seluruh perintah harian itu, beserta perilaku tepatnya, kita susun menjadi satu peta lengkap pada Chapter Menjalankan dan Mengelola Stack.
Referensi #
- Compose Watch
— cara kerja pemantauan berkas beserta contoh aturan
develop.watch. - develop — Compose file reference
— definisi kunci
watch,path,target,action, danignore. - docker compose watch — perilaku perintah watch dan hubungannya dengan perintah up.