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:

text
katalog/
├── compose.yaml
├── Dockerfile
├── package.json
├── nginx.conf
├── db_password.txt
├── .env
└── src/
    └── server.js

Dockerfile menyesuaikan dua barisnya:

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

yaml
  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: rebuild

Mari kita telaah kunci demi kunci blok di atas:

  1. develop: di samping kunci lain service — pemantauan adalah urusan service, bukan project; proxy dan db boleh tidak punya blok ini sama sekali.
  2. path: ./src — folder yang diawasi di host, dihitung relatif terhadap lokasi compose.yaml; Compose memantau folder itu beserta seluruh subdirektorinya secara rekursif.
  3. action: sync — ketika berkas di bawah ./src berubah, Compose menyalin berkas itu ke path container yang dituju target: /app/src — container tetap yang sama, image tetap yang sama, tidak ada rebuild.
  4. ignore: node_modules/ — pola yang dikecualikan dari pemantauan; folder node_modules di host — misalnya muncul karena tooling editor — tidak pernah tersalin ke container, karena dependensi container sudah terpasang dari RUN npm install di dalam image.
  5. action: rebuild untuk ./package.json — perubahan manifest dependensi tidak bisa diselesaikan salin-menyalin; Compose menjalankan build ulang penuh lalu membuat ulang container dari image hasilnya.
  6. action: rebuild untuk ./Dockerfile — alasan yang sama berlaku untuk resep build: suntingan pada Dockerfile hanya bermakna setelah di-build ulang.
  7. 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. Kombinasi sync dari Compose dan --watch dari aplikasi inilah yang membuat suntingan kode hidup tanpa rebuild.

Tiga jenis aksi yang tersedia, beserta varian kombinasinya, dirangkum pada tabel berikut:

AksiYang dilakukan ComposeCocok untuk
syncmenyalin berkas yang berubah ke target di containerkode sumber saat aplikasi punya reload sendiri
rebuildmembangun ulang image lalu membuat ulang containerpackage.json, Dockerfile, berkas build
restartme-restart container tanpa rebuildberkas yang dibaca entry point saat start
sync+restartmenyalin berkas lalu me-restart containerkode 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:

bash
$ docker compose up -d --build

Output yang Dihasilkan (dipotong):

text
[+] 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
bash
$ docker compose watch

Output yang Dihasilkan:

text
watching /home/budi/katalog/compose.yaml and subdirectories

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

bash
$ echo 'app.get("/jam", (_req, res) => res.send(new Date().toISOString()));' >> src/server.js

Terminal pemantauan merespons seketika:

text
Syncing web
Synced web
bash
$ curl localhost:8080/jam

Output yang Dihasilkan:

text
2026-10-14T08:41:07.482Z

Rute baru itu hidup pada detik yang sama. Mari kita telaah rantai peristiwa di baliknya:

  1. Compose mendeteksi perubahan pada ./src/server.js — aturan sync berlaku, berkas disalin ke /app/src/server.js di dalam container yang sedang berjalan; tidak ada satu baris build pada terminal, tidak ada Recreated.
  2. Proses Node memulai ulang dirinya sendiri — node --watch melihat berkas berubah dan me-restart aplikasi; ini adalah reload di tingkat proses, bukan di tingkat container.
  3. curl membuktikan hasil akhirnya — endpoint /jam menjawab dengan stempel waktu server, sesuatu yang mustahil ada pada proses lama.
  4. 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.
  5. 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:

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

Mari kita telaah jalur rebuild tersebut:

  1. Aturan rebuild terpicu oleh package.json — salin-menyalin satu berkas manifest tidak pernah memasang dependensi barunya; satu-satunya jalan benar adalah menjalankan ulang RUN npm install di dalam build.
  2. npm install kehilangan 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.
  3. Container dibuat ulang dari image baru — proses Recreated lalu Started terjadi otomatis tanpa satu perintah tambahan, dan node --watch kembali aktif dengan command yang sama.
  4. 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:

Aspeksync pada develop.watchBind mount source
Yang berpindahhanya berkas yang berubahseluruh path tertutup isi host
node_modulestetap milik image — hasil npm installhost harus menyediakan isinya sendiri
Arahsatu arah, host ke containerdua arah
Reaksi terhadap package.jsonbisa diatur — aturan rebuild terpisahtidak 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 #