Mengubah Docker dari kotak yang lambat dibangun menjadi mesin development yang mulus: bind mount source code dengan hot reload tanpa rebuild, tooling dev sebagai service Compose ber-profiles, perintah debugging kontainer, VS Code Dev Containers, docker compose watch, dan jebakan permission.

Setelah di episode 20 sebelumnya kita membawa aplikasi ke produksi — reverse proxy, TLS, zero-downtime deployment — kali ini kita mundur satu langkah ke meja kerja: bagaimana Docker dipakai sebagai environment pengembangan sehari-hari. Banyak orang baru mencoba Docker di tahap deploy, lalu kecewa karena workflow development-nya terasa lambat: setiap ganti satu baris kode harus build ulang image, restart kontainer, menunggu. Padahal paradigma itu keliru.
Kunci mental yang harus dipahami sejak awal: image untuk produksi, bind mount untuk development. Ketika men-debug di produksi, kalian menunggu build karena image immutable. Tapi di laptop, kalian tidak perlu mengubah image untuk mengganti kode — cukup petakan folder source code langsung ke dalam kontainer (bind mount), dan aplikasi di dalam kontainer akan melihat perubahan file seketika, persis seperti berjalan di host biasa. Ini mengubah Docker dari "kotak yang lambat dibangun" menjadi "mesin pengembangan yang reproducible": environment tim kalian identik, sementara alur kerja tetap secepat development tanpa container.
Inti workflow development modern adalah hot reload: aplikasi di dalam kontainer otomatis restart atau memuat ulang saat file berubah. Kombinasi yang tepat adalah bind mount (agar perubahan file terlihat) plus process manager/watch di dalam kontainer (agar aplikasi ikut berubah). Pilihan toolnya tergantung stack:
nodemon --watch src server.js — restart proses saat file di src berubah.npm run dev sudah menyertakan HMR bawaan; tidak butuh tool tambahan.go run -watch . (Go 1.24+) meng-compile ulang otomatis, atau tool komunitas seperti air untuk versi lama.Contoh service dev untuk aplikasi frontend Vite:
services:
app:
image: node:22-alpine
working_dir: /app
command: sh -c "npm ci && npm run dev"
ports:
- "5173:5173"
volumes:
- ./:/app
- /app/node_modules
environment:
- NODE_ENV=development
- VITE_API_BASE=http://localhost:8000
depends_on:
- db
- redisDua volume di atas adalah detail yang menentukan. ./:/app adalah bind mount — folder proyek kalian ditautkan langsung ke dalam kontainer, sehingga perubahan kode di editor langsung terlihat tanpa rebuild. Baris kedua, /app/node_modules, adalah anonymous volume yang "menutupi" node_modules di dalam folder host. Ini bukan kebetulan dan akan kita bedah tuntas di bagian pitfall.
Perhatikan juga bahwa tidak ada build: atau image hasil build — image node:22-alpine sudah cukup sebagai runtime development karena semua kode datang dari bind mount. Ini berlawanan dengan mentalitas "build image setiap ganti kode" yang sering menjadi alasan orang malas pakai Docker untuk development.
Environment development jarang cukup dengan satu service. Database, cache, dan tool pendukung seperti mail server palsu atau database admin adalah bagian dari meja kerja. Daripada menginstalnya di host (versi bisa beda antar mesin tim), jadikan semuanya service Compose. Di episode 11 kita belajar profiles; sekarang inilah tempatnya dipakai — tool yang hanya dibutuhkan saat development tidak boleh ikut naik di produksi:
services:
app:
image: node:22-alpine
working_dir: /app
command: sh -c "npm ci && npm run dev"
ports:
- "5173:5173"
volumes:
- ./:/app
- /app/node_modules
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
mailhog:
image: mailhog/mailhog:latest
profiles: ["tools"]
ports:
- "1025:1025"
- "8025:8025"
pgadmin:
image: dpage/pgadmin4:latest
profiles: ["tools"]
environment:
PGADMIN_DEFAULT_EMAIL: dev@example.com
PGADMIN_DEFAULT_PASSWORD: dev
ports:
- "5050:80"
depends_on:
- db
volumes:
pgdata:Service db, redis, dan app naik dengan perintah docker compose up -d biasa. Sementara mailhog (SMTP palsu untuk menguji email) dan pgAdmin hanya naik ketika benar-benar dibutuhkan: docker compose --profile tools up -d. Hasilnya, konfigurasi development satu file untuk seluruh tim, dan workflow produksi (episode 22) bisa memakai file compose terpisah yang tidak menyertakan tooling ini sama sekali.
Development tidak selalu mulus; yang membedakan engineer berpengalaman adalah kemampuan membaca keadaan kontainer secara sistematis. Docker menyediakan seperangkat perintah inspeksi yang mengungkap apa yang terjadi di dalam kontainer tanpa perlu menyentuh kodenya.
# Masuk ke shell kontainer yang berjalan (inspeksi langsung)
docker exec -it app sh
# Jalankan perintah sekali tanpa masuk shell
docker exec app node -e "console.log(process.env.DB_HOST)"
# Salin file keluar dari kontainer (misal log, dump)
docker cp app:/app/logs/app.log ./logs/
# Salin file ke dalam kontainer (hati-hati: tidak persisten)
docker cp ./.env.local app:/app/.env.localdocker exec adalah pintu masuk utama: kalian bisa memeriksa variabel environment, memastikan dependency terpasang, atau menelusuri proses yang berjalan. docker cp berguna untuk menarik artefak keluar (log, database dump hasil migrasi) atau memasukkan file sementara — ingat bahwa perubahan via docker exec/docker cp hilang saat container di-recreate, karena ia masuk ke layer tulis, bukan ke image atau volume.
# Lihat layer-layer penyusun image dan ukurannya
docker image history my-app:1.0
# Lihat hash layer lengkap tanpa dipotong
docker image history --no-trunc my-app:1.0
# Bandingkan filesystem kontainer dengan image asalnya
docker diff appdocker image history menunjukkan perintah Dockerfile yang membentuk tiap layer — episode 23 akan membedah ini lebih dalam. Sementara docker diff menjawab pertanyaan "apa yang berubah di dalam kontainer ini sejak lahir?" dengan awalan A (added), C (changed), dan D (deleted) per file. Ini sangat berguna untuk membuktikan bahwa sebuah service menulis file yang tidak kalian duga, misalnya cache atau log di dalam layer tulis.
# Attach stdin/stdout ke proses utama kontainer (hati-hati: kirim SIGINT)
docker attach app
# Penggunaan resource real-time (CPU, memori, I/O)
docker stats
# Proses yang berjalan di dalam kontainer
docker top app
# Log dengan rentang waktu dan batas baris
docker logs --since 10m --tail 100 -f appdocker stats adalah dashboard konsumsi resource per kontainer — jawaban pertama saat mencurigai kebocoran memori. docker top memperlihatkan proses di dalam namespace kontainer, bukan hanya PID utama. docker logs adalah alat debugging paling dipakai: kombinasi --since (rentang waktu), --tail (batas baris), dan -f (follow) membuat pencarian error di log panjang menjadi cepat.
Warning
Berhati-hatilah dengan docker attach: ia menghubungkan stdin/stdout kalian ke proses utama kontainer, sehingga menekan Ctrl+C mengirim SIGINT ke proses tersebut — bukan sekadar keluar dari sesi. Jika proses tidak menangani sinyal dengan baik, kontainer bisa berhenti. Untuk sekedar "masuk ke kontainer", docker exec -it app sh jauh lebih aman.
Ketidakcocokan environment antar mesin adalah sumber klasik "works on my machine". Dev Containers menyelesaikannya dengan radikal: seluruh environment pengembangan — runtime, tools, ekstensi VS Code, bahkan port — didefinisikan dalam kode dan dijalankan di dalam container. VS Code mengenali file .devcontainer/devcontainer.json dan secara otomatis membuka ulang workspace di dalam container tersebut.
{
"name": "app-dev",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "22"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [5173, 8000, 5432],
"mounts": [
"source=${localWorkspaceFolder}/secrets,target=/workspaces/app/secrets,type=bind"
],
"customizations": {
"vscode": {
"settings": {
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
},
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker"
]
}
},
"postCreateCommand": "npm ci && npm run typecheck",
"remoteUser": "node"
}Bedah elemen kuncinya:
image + features: base image yang sama untuk semua orang; features adalah modul resmi untuk menambahkan runtime (Node 22) dan kemampuan docker-in-docker tanpa menulis Dockerfile manual.mountWorkspaceFolder (default true): folder proyek di-mount ke container sehingga file kalian tersinkron otomatis; mounts menambah bind mount tambahan (contohnya folder secrets yang tidak boleh masuk git).forwardPorts: port aplikasi di dalam container otomatis diteruskan ke host, sama seperti -p.customizations.vscode: settings dan ekstensi VS Code diinstal di dalam container — setiap anggota tim otomatis mendapat ekstensi dan konfigurasi yang sama, menghilangkan "kenapa format-nya beda" di setiap mesin.postCreateCommand: perintah yang dijalankan sekali saat container pertama dibuat (instalasi dependency, typecheck). Menjamin "fresh clone langsung jalan".Nilai terbesarnya bukan kenyamanan, melainkan konsistensi dengan CI: kode yang kalian tulis di dev container diuji di lingkungan yang sama dengan pipeline CI/CD episode 19. Bug yang muncul karena perbedaan versi Node antara laptop dan CI hilang dengan sendirinya.
Bind mount sudah menyinkronkan file, tapi kadang kalian ingin mengontrol apa yang tersinkron dan kapan. docker compose watch menambahkan aturan eksplisit: sync file tertentu ke dalam kontainer, dan rebuild image saat file yang menentukan struktur aplikasi berubah.
services:
app:
image: node:22-alpine
working_dir: /app
command: sh -c "npm ci && npm run dev"
volumes:
- ./:/app
- /app/node_modules
develop:
watch:
- action: sync
path: ./src
target: /app/src
ignore:
- "**/node_modules"
- "**/dist"
- action: rebuild
path: package.jsonAturan action: sync menyalin perubahan file dari ./src ke target di dalam kontainer secara langsung — lebih hemat daripada bind mount penuh karena hanya file yang relevan yang dipantau, dan ignore bisa mengecualikan direktori besar. Aturan action: rebuild digunakan untuk file yang "menentukan bentuk image" seperti package.json — saat dependency berubah, image harus dibangun ulang, bukan sekadar di-sync. Jalankan dengan docker compose watch dan biarkan terminal terbuka; setiap perubahan kode otomatis tersinkron dan aplikasi hot-reload.
Tip
docker compose watch adalah penyempurnaan, bukan pengganti bind mount. Untuk kebanyakan proyek, bind mount + hot reload sudah cukup. Watch berguna saat volume bind mount tidak memadai — misalnya mengembangkan image container yang menjalankan daemon (bukan aplikasi web), di mana "sync file" dan "rebuild image" perlu dibedakan secara eksplisit.
Mismatch UID/GID (permission). File yang dibuat di dalam container sering dimiliki user root, sehingga tidak bisa diedit dari editor di host (yang berjalan sebagai user biasa), dan sebaliknya file yang dibuat host tidak bisa ditulis kontainer yang berjalan non-root. Solusi paling praktis di Compose: tentukan user: "${UID:-1000}:${GID:-1000}" pada service dev, atau gunakan image yang mengekspos user non-root (seperti user node pada image Node resmi) dan samakan izin folder kerja. Di dev container, remoteUser menangani hal ini secara transparan.
node_modules di bind mount vs di dalam container. Ini jebakan tersulit. Saat bind mount menutupi folder /app dengan folder host, node_modules di dalam image hilang — kontainer memakai node_modules milik host. Masalahnya, native binary seperti esbuild atau sharp di-compile per platform: node_modules hasil npm ci di macOS tidak jalan di container Linux. Karena itu pola - /app/node_modules (anonymous volume) dipakai: Docker menutupi folder tersebut di dalam kontainer, menyembunyikan node_modules host, sehingga npm ci di dalam kontainer menghasilkan binary yang cocok dengan Linux container. Setiap kali dependency berubah, jalankan ulang npm ci di dalam container (atau gunakan watch action: rebuild).
Polling vs native filesystem events. Docker Desktop di macOS/Windows dan filesystem jarak jauh (NFS, WSL2, network drive) sering tidak meneruskan event perubahan file (inotify) secara andal ke dalam kontainer — akibatnya hot reload "diam" padahal kode sudah berubah. Solusinya beralih ke polling: set CHOKIDAR_USEPOLLING=true (Nodemon/Chokidar), VITE_USE_POLLING=true (Vite), atau --watch-poll (Go). Harga yang dibayar: polling memakan CPU, jadi aktifkan hanya di environment yang memang bermasalah. Di Linux native, tidak perlu.
Pada episode 21 ini kita membalik paradigma: Docker bukan hanya alat produksi, melainkan mesin development yang membuat environment seluruh tim identik. Kita mempelajari bind mount + hot reload (tanpa rebuild), tooling dev sebagai service Compose dengan profiles, perintah debugging kontainer (docker exec, docker cp, docker diff, docker stats, docker top, docker logs), VS Code Dev Containers yang reproducible, dan docker compose watch untuk sinkronisasi terkelola. Kita juga membedah tiga pitfall kunci: mismatch UID/GID, node_modules yang harus diisolasi lewat anonymous volume, dan polling vs native filesystem events.
Inti yang harus kalian bawa:
docker exec, docker logs, docker stats untuk membaca keadaan kontainer.- /app/node_modules anonymous volume dan CHOKIDAR_USEPOLLING adalah dua penyelamat workflow frontend.Di episode 22 selanjutnya kita akan merakit semuanya menjadi satu: studi kasus arsitektur production-grade — frontend, API, PostgreSQL, Redis, Traefik, dan monitoring (cAdvisor + Prometheus + Grafana) dalam satu Compose produksi lengkap dengan checklist kesiapan produksi dan troubleshooting masalah nyata. Sampai jumpa di episode 22!