Pola dari Chapter Bind Mount Source Code menempatkan seluruh berkas katalog-web di tempat yang tepat: kode di host, dependensi di volume, aplikasi berjalan di container, dan suntingan editor tampil seketika di /app di dalam container. Namun di akhir chapter itu tersisa satu kejengkelan yang menohok: curl localhost:8080 masih menjawab teks lama setelah Budi menyimpan suntingan, karena proses Node yang berjalan di dalam container terus memegang kode versi lama di memorinya. Bind mount mengurus perpindahan berkas, tetapi memuat ulang kode adalah urusan yang sepenuhnya berbeda — dan tanpa alat tambahan, satu-satunya jalan adalah menghentikan proses lalu menjalankannya lagi dari awal. Chapter ini menutup jarak terakhir menuju siklus edit-uji yang benar-benar mulus: kita akan membuktikan dulu mengapa proses Node bandel, lalu menyerahkan tugas pengawasan itu kepada nodemon yang berjalan di dalam container, membandingkan hasilnya dengan develop.watch, dan merangkai ritme kerja dua terminal yang menjadi denyut nadi development sehari-hari.
Kode Baru yang Tidak Dibaca Proses Lama #
Mari kita saksikan dulu masalahnya secara jujur, karena perilaku ini akan kita temui berkali-kali sepanjang karier. Stack katalog-web masih menyala dengan command bawaan image dev — node server.js — dan Budi menyimpan satu suntingan kecil pada teks ucapan selamat datang:
$ sed -i 's/versi pengembangan/versi kedua/' src/server.js
$ curl localhost:8080Output yang Dihasilkan:
Halo dari katalog-web versi pengembanganBerkas di /app/server.js di dalam container sudah memuat teks baru — itu sudah kita buktikan dengan grep pada chapter sebelumnya — tetapi aplikasi tetap menjawab teks lama. Mari kita telaah mengapa:
- Module loader Node membaca berkas sekali — saat
node server.jsmenyala, seluruh modul yang diimpor dibaca, diurai, lalu hasilnya disimpan di require cache di memori proses; berkas aslinya tidak dibaca ulang. - Bind mount bukan pemicu muat ulang — bind mount hanya menjamin isi path yang sama; ia tidak punya andil sama sekali atas apa yang dilakukan proses terhadap isi tersebut.
- Menyalakan ulang proses adalah satu-satunya pemulihan — proses baru membaca berkas dari awal, mengisi cache baru, dan kode segar pun hidup.
Karena itu setiap workflow development yang sehat untuk aplikasi semacam ini selalu berpasangan dua komponen: mekanisme yang membawa berkas (bind mount atau sync) dan mekanisme yang memaksa proses memuat ulang (watch mode). Salah satu saja menghasilkan pengalaman setengah jadi yang justru lebih membingungkan.
nodemon Menjaga Proses Tetap Segar #
Alat pengawas klasik untuk aplikasi Node.js adalah nodemon — dan nodemon sudah lama tercatat di devDependencies pada package.json katalog-web, terpasang oleh npm ci di image dev, menunggu dimanfaatkan. Menjalankannya di dalam container menuntut satu penyesuaian kecil pada Dockerfile: binary nodemon terpasang di node_modules/.bin, dan folder itu tidak berada di PATH:
FROM node:22-alpine AS base
WORKDIR /app
COPY src/package*.json ./
FROM base AS dev
RUN npm ci
ENV PATH="/app/node_modules/.bin:${PATH}"
USER node
CMD ["node", "server.js"]Baris baru ENV PATH menambahkan folder binary lokal ke PATH sehingga nodemon bisa dipanggil langsung. Sebenarnya npm run dev — script yang sudah ada sejak repositori ini lahir — juga menyediakan jalur yang sama, karena npm menambahkan node_modules/.bin ke PATH setiap kali menjalankan script; menyetel PATH di image membuat kedua jalur sama-sama siap. Satu suntingan terakhir di compose.yaml mengganti command service web:
web:
build:
context: .
target: dev
image: katalog-web:dev
command: nodemon server.js
ports:
- "8080:3000"
environment:
DATABASE_URL: postgres://katalog:rahasia123@db:5432/katalog
volumes:
- ./src:/app
- node_modules:/app/node_modules
depends_on:
- dbKunci command menimpa CMD image — perilaku override yang kita kenal dari Chapter Command Override — sehingga proses di dalam container kini dijalankan di bawah pengawasan nodemon, bukan node polos. Container diperbarui dengan satu rebuild:
$ docker compose up -d --buildOutput yang Dihasilkan (dipotong):
[+] Building 6.1s (13/13) FINISHED
=> [4/6] RUN npm ci 0.0s (cached)
=> [5/6] ENV PATH=/app/node_modules/.bin 0.0s
...
[+] Running 2/2
✔ Container katalog-web-db-1 Running 0.0s
✔ Container katalog-web-web-1 Recreated
✔ Container katalog-web-web-1 Started$ curl localhost:8080Output yang Dihasilkan:
Halo dari katalog-web versi keduaMari kita telaah dua hasil sekaligus: layer RUN npm ci tampil sebagai cached karena manifest tidak berubah — baris ENV PATH berada di bawahnya sehingga cache layer dependensi tidak terganggu — dan proses yang baru menyala kini membaca kode terbaru, sehingga suntingan “versi kedua” yang tadi terabaikan langsung hidup. Skrip dev di package.json tetap tersedia sebagai npm run dev bila suatu saat container dijalankan tanpa compose.
Menyaksikan Siklus Hot Reload #
Sekarang demonstrasi inti chapter ini — siklus lengkap dari simpanan editor sampai jawaban HTTP. Budi menyimpan satu lagi perubahan teks:
$ sed -i 's/versi kedua/versi ketiga/' src/server.jsBeberapa ratus milidetik kemudian, log container menceritakan apa yang terjadi:
$ docker compose logs webOutput yang Dihasilkan (dipotong):
katalog-web-web-1 | [nodemon] restarting due to changes...
katalog-web-web-1 | [nodemon] starting `node server.js`
katalog-web-web-1 | katalog-web mendengarkan di port 3000$ curl localhost:8080Output yang Dihasilkan:
Halo dari katalog-web versi ketigaMari kita telaah rantai peristiwa di balik tiga baris log tersebut:
- nodemon mengawasi berkas di dalam container — pengawasan berjalan pada path
/appyang merupakan bind mount, sehingga suntingan di host terlihat sebagai perubahan berkas di container. restarting due to changesmenandai proses lama dibunuh — nodemon menghentikan prosesnodelama, bukan container-nya; container tetap hidup, image tetap sama, dan tidak satu baris build pun dieksekusi.startingmenjalankan proses baru — proses Node yang baru membaca seluruh kode dari awal, termasuk require cache yang segar.curlmenerima buktinya — teks “versi ketiga” hanya mungkin dijawab oleh proses yang membaca berkas hasil suntingan.- Biaya per iterasi jatuh ke ratusan milidetik — bandingkan dengan siklus rebuild pada Chapter Compose Watch yang memakan detik; keduanya sama-sama otomatis, tetapi kelas masalahnya berbeda: nodemon untuk kode yang dibaca proses, rebuild untuk manifest dependensi.
Compose Watch sebagai Pembanding #
Sebelum menutup siklus ini, mari kita kembali sebentar ke tetangga dari section Docker Compose: blok develop.watch pada Chapter Compose Watch. Keduanya memecahkan masalah yang sama — membawa suntingan ke container yang sedang berjalan — dengan karakter yang berbeda:
| Aspek | develop.watch aksi sync | Bind mount + nodemon |
|---|---|---|
| Yang berpindah | hanya berkas yang berubah | seluruh path selalu terbuka |
node_modules | tetap milik image | tetap milik volume |
| Arah | satu arah, host ke container | dua arah |
Reaksi pada package.json | aturan rebuild terpisah yang otomatis | tidak ada — rebuild manual |
| Alat tambahan di image | tidak perlu | watch mode wajib tersedia |
Gabungan keduanya juga sah dan lumayan umum: bind mount menghadirkan kode dua arah, sementara aturan rebuild pada package.json dan Dockerfile memastikan perubahan resep dan dependensi tetap ditangani otomatis — persis pola kombinasi yang kita rakit di Chapter Compose Watch. Panduan praktisnya sederhana: bind mount + watch mode adalah konfigurasi paling umum karena hanya butuh satu baris command dan alat pengawas standar milik ekosistem aplikasi; develop.watch lebih terkontrol karena image tetap menjadi sumber kebenaran dan tiap kelas berkas punya jalur reaksi eksplisit — pilihan yang bersinar ketika tim ingin perilaku seragam lintas bahasa pemrograman dalam satu stack.
Hot Reload di Sisi Front-end #
Untuk kerja front-end, mekanismenya naik kelas lagi. Dev server Vite atau webpack berjalan di dalam container dengan portnya diterbitkan — misalnya "5173:5173" untuk Vite — dan browser di host tersambung ke dev server itu lewat koneksi websocket. Saat berkas komponen berubah, dev server mengganti modul di browser tanpa memuat ulang halaman penuh; teknik ini disebut hot module replacement, dan state aplikasi di layar — isi form, posisi scroll, variabel komponen — bertahan melewati penggantian. Bind mount bertugas membawa suntingan ke container, dev server bertugas mengawasi dan mengganti modul, dan browser cukup menyegarkan sekali di awal sesi. Pola ini kita pakai utuh ketika katalog-web kelak menumbuhkan sisi klien pada chapter-chapter berikutnya.
Ritme Dua Terminal #
Hot reload mengubah kebiasaan terminal menjadi ritme tetap dua jendela. Terminal pertama ditempatkan di samping editor dan hanya menjalankan satu perintah yang hidup sepanjang sesi:
$ docker compose logs -f webOutput yang Dihasilkan (dipotong):
katalog-web-web-1 | katalog-web mendengarkan di port 3000
katalog-web-web-1 | [nodemon] restarting due to changes...
katalog-web-web-1 | [nodemon] starting `node server.js`Terminal kedua menjadi terminal kerja: menyimpan suntingan dari editor, mengetuk endpoint lewat curl, menjalankan git commit, dan kembali ke editor. Log mengalir sendiri di jendela pertama setiap kali nodemon menyala ulang atau aplikasi mencetak sesuatu — kebiasaan logs -f yang kita bangun sejak Chapter Melihat Log Container, kini menjadi jendela refleks dari setiap simpanan. Ketika aplikasi membisu di saat harusnya sibuk, dua jendela ini juga menjadi alat diagnosis pertama: apakah nodemon benar-benar menyala ulang, dan apa yang dicetak proses yang baru?
Menyalakan Ulang dengan Tepat #
Sebelum chapter ini usai, satu pembedahan kecil menyelamatkan kita dari keputusan restart yang salah. Ada tiga jenis “menyalakan ulang” di dunia compose, dan ketiganya sering tertukar:
$ docker compose restart webOutput yang Dihasilkan:
[+] Restarting 1/1
✔ Container katalog-web-web-1 Startedrestart menjalankan ulang proses di container yang sama — cepat, tetapi juga dangkal: compose.yaml tidak dibaca ulang, image tidak dibangun ulang, dan container tidak dibuat ulang. Karena itu aturan praktisnya:
| Mekanisme | Pemicu | Yang berubah | Kapan dipakai |
|---|---|---|---|
docker compose restart web | perintah manual | proses dalam container yang sama | proses macet, konfigurasi startup perlu dibaca ulang |
| nodemon / watch mode | berkas kode berubah | hanya proses aplikasi | iterasi kode harian |
develop.watch aksi sync | berkas berubah di host | isi berkas di container, proses bila aturannya sync+restart | kontrol per kelas berkas |
| Hot module replacement | modul front-end berubah | modul di browser | kerja front-end |
docker compose up -d --build | manifest atau Dockerfile berubah | image, lalu container dibuat ulang | dependensi baru |
Ritme harian kini benar-benar mulus: suntingan mengalir, nodemon menyala ulang, log bercerita, dan jawaban HTTP selalu segar. Namun ada satu pertanyaan yang belum terjawab dan semakin nyata setiap kali kita menatap package.json: nodemon — pengawas yang kita andalkan setiap hari ini — tercatat di devDependencies, sementara aplikasi yang berjalan di produksi tidak membutuhkannya sama sekali. Dependensi macam apa yang boleh masuk image dev, mana yang wajib tinggal di image produksi, dan bagaimana satu Dockerfile melayani keduanya sekaligus adalah materi Chapter Development Dependencies.
Referensi #
- Use file watch mode
— perbandingan aksi
sync,rebuild, dansync+restartterhadap bind mount. - develop — Compose file reference
— definisi kunci
watchdan aturannya per kelas berkas. - docker compose logs — CLI Reference
— perilaku
logs -fsebagai jendela pemantauan dua terminal. - docker compose restart — CLI Reference — cakupan perintah restart dan batasnya terhadap perubahan deklarasi.