Bind Mount #
Dalam pengelolaan container Docker, penyimpanan data adalah topik yang sangat penting, terutama ketika kita berurusan dengan source code, configuration file, atau data yang harus langsung terhubung dengan sistem host. Salah satu mekanisme yang sering digunakan — namun sering juga disalahpahami — adalah bind mount.
Bind mount memungkinkan sebuah container mengakses langsung direktori atau file yang ada di host OS, tanpa lapisan abstraksi tambahan seperti Docker volume. Karena sifatnya yang “langsung ke host”, bind mount sangat powerful untuk produktivitas developer, tetapi juga memiliki implikasi terhadap keamanan, portabilitas, dan konsistensi environment.
Artikel ini membahas bind mount secara mendalam: definisi, cara kerja di level OS, sintaks CLI dan Compose, use case nyata, perbedaan dengan volume, risiko yang harus diwaspadai, dan best practice penggunaannya.
Apa Itu Bind Mount? #
Bind mount adalah mekanisme Docker untuk me-mount:
- File, atau
- Direktori
langsung dari filesystem host ke dalam filesystem container.
Berbeda dengan Docker volume yang dikelola penuh oleh Docker, bind mount:
- Menggunakan path absolut di host (atau path relatif terhadap
docker-compose.yml). - Bergantung pada struktur direktori host — path harus ada di host.
- Tidak dikelola lifecycle-nya oleh Docker — file di host ada di luar kontrol Docker.
- Perubahan di host langsung terlihat di container, dan sebaliknya.
flowchart LR
subgraph HOST["Host OS"]
HP[/home/dev/project/src/]
HC[./nginx.conf]
end
subgraph CONT["Container"]
CP[/usr/src/app/]
CC[/etc/nginx/nginx.conf]
end
HP <-->|bind mount| CP
HC <-->|bind mount| CC
style HP fill:#a8d8a8
style CP fill:#ffd8a8
Sederhananya:
Bind mount = container membaca & menulis langsung ke filesystem host.
Tidak ada copy data. Tidak ada sync. Yang ada adalah satu filesystem, dua perspektif — host dan container melihat path yang sama.
Cara Kerja Bind Mount di Level OS #
Untuk benar-benar memahami bind mount, kamu perlu tahu apa yang terjadi di level kernel. Docker berjalan di atas kernel Linux (secara native atau via VM di macOS/Windows), dan bind mount memanfaatkan fitur kernel berupa mount namespace.
Mount Namespace #
Mount namespace adalah fitur kernel Linux yang mengisolasi filesystem view untuk sekumpulan proses. Container berjalan di mount namespace sendiri — apa yang dia lihat sebagai / mungkin berbeda dengan apa yang host lihat sebagai /.
Bind mount bekerja dengan membuat mount point baru di dalam mount namespace container, yang menunjuk ke path tertentu di host.
flowchart TB
subgraph KERNEL["Linux Kernel"]
NS[Mount Namespace<br/>Container]
HOST_FS[Host Filesystem<br/>/home/dev/project/src/]
end
NS -->|/usr/src/app → /home/dev/project/src| HOST_FS
Alurnya:
- Docker daemon menerima instruksi bind mount.
- Kernel membuat mount point baru di namespace container.
- Mount point menunjuk ke path di host.
- Container melihat path itu sebagai direktori biasa di filesystem internalnya.
Yang penting: tidak ada copy data. Saat container membaca file, kernel langsung mengarahkannya ke file di host. Saat container menulis file, perubahan langsung ke filesystem host.
Mount Propagation #
Linux mount namespace punya konsep mount propagation yang menentukan bagaimana mount events di satu namespace dikirim ke namespace lain. Untuk Docker, opsi ini jarang diatur manual, tapi penting dipahami untuk kasus advanced:
| Propagation | Penjelasan |
|---|---|
private (default) |
Mount tidak di-propagate ke namespace lain |
rshared |
Mount events di-propagate dua arah |
rslave |
Mount events diterima, tidak dikirim |
shared |
Mount events di-propagate dua arah (sub-namespace) |
Untuk kebanyakan use case, default private cukup. Tapi untuk kasus seperti Kubernetes yang mount volume dari host ke pod, propagation perlu dikonfigurasi dengan benar.
Sintaks Bind Mount #
Ada dua format utama untuk mount di Docker CLI: -v (singkat) dan --mount (eksplisit). Keduanya bekerja untuk volume dan bind mount, tapi dengan sintaks yang sedikit berbeda.
Format -v
#
docker run -d \
--name dev-app \
-v /home/dev/project/src:/usr/src/app \
node:20
Format: -v [host_path]:[container_path]:[options]
host_path— path absolut di host.container_path— path di dalam container.options— opsional, sepertiro(read-only),rw(read-write, default).
Jika path host tidak ada, Docker akan membuatnya secara otomatis. Ini bisa menjadi sumber bug — direktori tercipta karena typo.
Format --mount (Lebih Eksplisit)
#
docker run -d \
--name dev-app \
--mount type=bind,source=/home/dev/project/src,target=/usr/src/app \
node:20
Format: --mount type=bind,source=...,target=...,readonly
Kelebihan --mount:
- Setiap komponen ditulis eksplisit.
- Tidak ada ambiguitas antara named volume dan bind mount.
- Lebih mudah di-script dan di-debug.
Read-Only Bind Mount #
Untuk file atau direktori yang tidak boleh diubah container:
docker run -d \
--name app \
-v ./nginx.conf:/etc/nginx/nginx.conf:ro \
nginx
Container bisa baca konfigurasi Nginx, tapi tidak bisa mengubahnya. Host adalah source of truth.
:roadalah deep read-only. Semua file di dalam path menjadi read-only, bukan hanya file individual. Ini berbeda denganchmod 555di level file, karena Docker enforce di level mount.
Path Relatif di Docker Compose #
Di Docker Compose, path relatif terhadap lokasi file docker-compose.yml, bukan terhadap working directory shell:
services:
app:
volumes:
- ./src:/app/src # ./src relatif terhadap file compose
- ./config:/app/config:ro
- /home/user/data:/data # path absolut
Ini membuat compose file lebih portabel — bisa di-share ke tim tanpa harus menyesuaikan path.
Cara Penggunaan Bind Mount #
Local Development — Use Case Paling Umum #
Use case paling populer untuk bind mount: mount source code lokal ke container agar container langsung menjalankan perubahan kode.
Node.js / Frontend #
docker run -d \
--name dev-node \
-v $(pwd)/src:/app/src \
-p 3000:3000 \
node:20
Edit file di src/ di host → container langsung melihat perubahan → restart service / hot reload otomatis.
Go Backend #
docker run -d \
--name dev-go \
-v $(pwd)/cmd:/app/cmd \
-v $(pwd)/internal:/app/internal \
-v $(pwd)/go.mod:/app/go.mod \
-p 8080:8080 \
golang:1.22
Dengan Air atau CompileDaemon, container auto-rebuild dan restart saat source code berubah.
Python / Django #
docker run -d \
--name dev-django \
-v $(pwd):/app \
-p 8000:8000 \
python:3.12 \
python manage.py runserver 0.0.0.0:8000
Django dev server auto-reload saat file Python berubah.
Hot Reload Framework #
Banyak framework modern punya hot reload yang bergantung pada filesystem watcher:
- Nodemon (Node.js) — watch file, restart service.
- Vite (Vue/React) — HMR untuk frontend.
- Air (Go) — live reload untuk Go.
- Flask debug mode — auto-reload Python.
- Rails — reload on file change.
Semuanya butuh bind mount agar file watcher bisa melihat perubahan dari host.
Akses File Konfigurasi #
Mount file konfigurasi lokal ke container tanpa bake ke image:
docker run -d \
--name nginx \
-v ./nginx.conf:/etc/nginx/nginx.conf:ro \
-v ./sites-enabled:/etc/nginx/sites-enabled:ro \
-p 80:80 \
nginx
Edit nginx.conf di host → restart container → konfigurasi baru aktif. Tidak perlu rebuild image.
Live Editing Config #
Untuk aplikasi yang perlu debug konfigurasi:
services:
app:
image: my-app:dev
volumes:
- ./.env:/app/.env:ro
- ./config.yaml:/app/config.yaml:ro
Edit .env di host, restart container, lihat efek. Cepat untuk iterasi.
Sharing Data dengan Tools Host #
Untuk development yang butuh integrasi dengan tool di host:
# Mount hasil download Chrome dari host
docker run -d \
-v ~/.cache/puppeteer:/app/.cache/puppeteer \
my-scraper
# Mount SSH key untuk Git operations
docker run -d \
-v ~/.ssh:/root/.ssh:ro \
my-build-image
Docker Compose dengan Bind Mount #
Docker Compose adalah cara paling ergonomis untuk menggunakan bind mount. Path relatif, deklaratif, dan reproducible.
Contoh Lengkap #
version: "3.9"
services:
app:
build: ./app
ports:
- "3000:3000"
volumes:
- ./app/src:/app/src # source code
- ./app/public:/app/public # static files
- ./nginx.conf:/etc/nginx/nginx.conf:ro # config
environment:
- NODE_ENV=development
command: npm run dev
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: dev
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # init script
- pgdata:/var/lib/postgresql/data # persistent data
volumes:
pgdata:
Penting: Path Relatif #
volumes:
- ./src:/app/src # relatif terhadap file compose
- ${HOME}/data:/data # env var di-expand
- /opt/data:/data # path absolut
Compose me-resolve path relatif ke working directory-nya file compose, bukan CWD shell. Ini membuat compose file portabel antar developer.
Long Syntax di Compose #
volumes:
- type: bind
source: ./src
target: /app/src
read_only: false
Long syntax lebih verbose tapi lebih eksplisit — sama seperti --mount di CLI.
Hybrid: Bind Mount + Named Volume #
Pattern umum untuk development:
services:
app:
build: .
volumes:
- ./src:/app/src # bind mount, source code
- node_modules:/app/node_modules # volume, dependency
- ./.env:/app/.env:ro # bind mount, config
ports:
- "3000:3000"
volumes:
node_modules: # di-mount ke volume, supaya tidak overwrite dengan folder kosong dari host
Logikanya:
- Source code di-bind mount — perubahan di host langsung terlihat di container.
- node_modules di-mount ke volume — supaya tidak overwrite dengan folder kosong dari host (yang sering terjadi jika developer lupa
npm installdi host). - .env di-bind mount read-only — config dari host, container tidak boleh mengubah.
Ini adalah pola yang sangat umum di Docker Compose untuk Node.js development.
Perbedaan Detail Bind Mount vs Volume #
| Aspek | Bind Mount | Docker Volume |
|---|---|---|
| Lokasi data | Ditentukan user (path host) | Dikelola Docker |
| Ketergantungan host | Tinggi | Rendah |
| Portabilitas | Rendah (path host harus konsisten) | Tinggi (managed Docker) |
| Performa | Sangat cepat (native filesystem) | Cepat (sedikit overhead) |
| Keamanan | Lebih berisiko (container bisa hapus file host) | Lebih aman (Docker enforce permission) |
| Cocok untuk | Dev & debug | Production |
| File host bisa diakses langsung | Ya | Tidak (harus via Docker) |
| Source path harus ada | Ya, akan dibuat jika tidak ada | Tidak perlu, Docker yang buat |
| Volume driver support | Tidak | Ya (NFS, EFS, cloud) |
| Inspect dari host | Langsung (ls, cd, edit) |
Harus ke direktori /var/lib/docker/volumes/ |
| Sharing path kosong | Folder host akan di-mount (kosong di container) | Volume di-init dengan benar |
| Override image content | Ya (path container jadi isi host) | Tidak (image content tetap) |
flowchart TB
P[Penyimpanan Docker] --> B[Bind Mount]
P --> V[Volume]
B --> B1[Path host eksplisit]
B --> B2[Akses langsung dari host]
B --> B3[Untuk development]
V --> V1[Path host internal Docker]
V --> V2[Akses via Docker CLI]
V --> V3[Untuk production]
style B fill:#a8d8a8
style V fill:#ffd8a8
Kasus Sensitif: Mount ke Folder Kosong di Host #
Ini adalah bug yang sering terjadi dengan bind mount:
# Host folder ./node_modules tidak ada
docker run -v ./node_modules:/app/node_modules my-app
# Docker membuat folder kosong di host
# Container melihat /app/node_modules KOSONG
# Padahal image punya node_modules asli!
Solusi: Gunakan named volume untuk node_modules (seperti di pola hybrid sebelumnya), atau buat folder kosong yang tidak konflik.
Risiko dan Kekurangan Bind Mount #
1. Keamanan #
Container dengan bind mount bisa:
- Menghapus file host — kalau container compromised atau bug,
rm -rfdi dalam container akan menghapus file di host. - Mengubah permission — container root bisa
chmodfile host. - Mengakses data sensitif — bind mount
/etc,~/.ssh, atau/var/run/docker.sockmemberikan akses yang sangat luas.
Contoh berbahaya:
# JANGAN: bind mount socket Docker ke container
docker run -v /var/run/docker.sock:/var/run/docker.sock my-tool
# Container sekarang bisa control Docker daemon = host penuh
# JANGAN: bind mount root filesystem
docker run -v /:/host my-tool
# Container bisa hapus SELURUH host
Anti-pattern kritis: Mount/var/run/docker.sockatau root filesystem/adalah container escape yang sering dieksploitasi. Container dengan akses ini bisa compromise seluruh host. Selalu pikirkan implikasi keamanan sebelum bind mount ke path kritis.
2. Tidak Portabel #
Bind mount sangat bergantung pada struktur filesystem host. Compose file yang jalan di laptop kamu belum tentu jalan di laptop rekan tim:
# Compose file kamu
volumes:
- /home/you/project:/app
# Yang diharapkan rekan tim
volumes:
- /home/colleague/project:/app
Path host harus konsisten. Untuk menghindari ini:
- Gunakan path relatif di Compose (
./src:/app/src). - Dokumentasikan struktur direktori yang diharapkan.
- Gunakan entrypoint script untuk setup yang robust.
3. Perbedaan di macOS & Windows #
Di Linux, bind mount adalah native filesystem call. Cepat, handal, identik dengan filesystem host.
Di macOS dan Windows, Docker berjalan di atas Linux VM (biasanya via VirtIO atau gRPC-FUSE). Bind mount melewati VM, dan ada beberapa quirk:
- Performa lebih lambat — terutama untuk filesystem operation kecil (banyak file watcher calls).
- File watcher tidak stabil — perubahan file kadang tidak trigger event di container (tergantung filesystem sharing implementation).
- Permission berbeda — UID/GID di dalam container mungkin tidak match dengan user di host.
- Case sensitivity — macOS default case-insensitive, Linux case-sensitive. Bug kadang muncul.
flowchart TB
subgraph LINUX["Linux Host"]
A1[Bind mount] --> A2[Native FS call]
A2 --> A3[Cepat, handal]
end
subgraph MAC["macOS / Windows Host"]
B1[Bind mount] --> B2[VM + 9P / gRPC-FUSE]
B2 --> B3[Lebih lambat, ada quirk]
end
style LINUX fill:#a8d8a8
style MAC fill:#ffd8a8
4. File Permission Issues #
Container biasanya jalan dengan user tertentu (default root jika tidak di-USER). Mount file host dengan permission berbeda bisa menyebabkan:
- File tidak bisa ditulis oleh container (
Permission denied). - File baru di dalam bind mount punya ownership host user, bukan container user.
- Saat di-commit, permission berubah.
Solusi:
USERdi Dockerfile dengan UID yang sama dengan host user.chowndi entrypoint script.fixuidataugosuuntuk handle UID mapping.
5. Race Condition Saat Start #
Container yang start sebelum mount siap kadang error. Misalnya, script di entrypoint mencoba baca file yang di-bind mount, tapi mount belum kelar.
Solusi: entrypoint script yang wait untuk mount ready.
#!/bin/sh
# entrypoint.sh
until [ -f /app/config/config.yaml ]; do
echo "Waiting for config mount..."
sleep 1
done
exec my-app
Kapan TIDAK Menggunakan Bind Mount #
Hindari bind mount di situasi berikut:
- Production environment — kecuali kamu benar-benar paham implikasinya.
- Data yang harus aman & konsisten — volume lebih aman.
- Aplikasi butuh portability tinggi — path host harus konsisten di semua environment.
- Deployment multi-host / orchestration (Kubernetes) — bind mount tidak scalable.
- Cross-host data sharing — bind mount terikat pada satu host.
Untuk situasi ini, gunakan Docker volume atau object storage (S3, GCS).
flowchart TD
A{Bind mount atau<br/>Volume?}
A -->|Development| B[Bind Mount]
A -->|Production| C[Volume]
B --> B1[Live reload source code]
B --> B2[Mount config]
C --> C1[Database persistent]
C --> C2[File upload]
C --> C3[Multi-container sharing]
style A fill:#ffd8a8
style C fill:#51cf66,color:#fff
Best Practice Bind Mount #
1. Gunakan Hanya untuk Development #
Bind mount adalah tool development. Untuk production, volume lebih tepat.
2. Hindari Mount Path Kritis #
JANGAN bind mount:
/(root filesystem)/var/run/docker.sock(Docker daemon socket)/etc,/boot,/sys,/proc(system paths)- Direktori system host lainnya
3. Gunakan Opsi :ro untuk Konfigurasi
#
-v ./config:/app/config:ro
Config adalah source of truth dari host. Container tidak boleh mengubah.
4. Kombinasikan dengan Volume untuk Data Penting #
volumes:
- ./src:/app/src # bind mount untuk source code
- app-data:/app/data # volume untuk data persisten
Pemisahan ini memungkinkan live reload tanpa risiko kehilangan data.
5. Pastikan Permission User Konsisten #
Jika container jalan sebagai user dengan UID 1000, dan host file dimiliki oleh UID 1000, mount akan bekerja dengan baik. Jika tidak, akan ada permission issue.
Di Dockerfile:
RUN groupadd -g 1000 app && useradd -u 1000 -g app app
USER app
Di docker-compose.yml:
user: "1000:1000"
6. Gunakan Path Relatif di Compose #
volumes:
- ./src:/app/src # portabel
- /abs/path:/data # tidak portabel
Path relatif membuat compose file bisa di-share ke seluruh tim tanpa modifikasi.
7. Hindari Mount yang Tidak Perlu #
Setiap bind mount menambah kompleksitas. Mount hanya yang benar-benar dibutuhkan:
# BENAR: hanya yang perlu
volumes:
- ./src:/app/src
# BERLEBIH: mount terlalu banyak
volumes:
- ./src:/app/src
- ./config:/app/config
- ./logs:/app/logs
- ./cache:/app/cache
- ./tmp:/tmp
8. Test di Environment yang Mirip Production #
Walaupun development pakai bind mount, test di environment yang mirip production (dengan volume) untuk memastikan aplikasi bekerja dengan benar tanpa bind mount.
9. Dokumentasikan Mount Point #
Setiap bind mount harus punya catatan:
Bind mount: ./src → /app/src
Tujuan: live reload source code
File yang di-share: source code Node.js
Permission: host user UID 1000
10. Pertimbangkan Docker Compose Watch (Modern) #
Docker Compose 2.22+ punya fitur watch yang lebih advanced dari bind mount biasa:
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.json
Watch memberikan kontrol lebih granular: sync file tertentu, rebuild untuk perubahan tertentu. Lebih robust dari bind mount polos.
Pola Penggunaan Umum #
Pola 1: Development Standar (Node.js) #
services:
app:
build: ./app
ports:
- "3000:3000"
volumes:
- ./app/src:/app/src
- ./app/public:/app/public
- node_modules:/app/node_modules
- ./.env:/app/.env:ro
environment:
- NODE_ENV=development
command: npm run dev
volumes:
node_modules:
- Source code — bind mount untuk live reload.
- Public assets — bind mount untuk development asset.
- node_modules — volume untuk menghindari overwrite.
- .env — bind mount read-only untuk config.
Pola 2: Multi-Service Development #
services:
app:
build: ./app
volumes:
- ./app:/app
ports:
- "3000:3000"
depends_on:
- db
- redis
worker:
build: ./worker
volumes:
- ./worker:/worker
depends_on:
- rabbitmq
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
- redis-data:/data
volumes:
pgdata:
redis-data:
App dan worker masing-masing bind mount source code. Database dan Redis pakai volume (data persisten). Hybrid approach yang ideal.
Pola 3: Konfigurasi Dinamis #
services:
app:
image: my-app:1.2
volumes:
- ./config/production.yaml:/app/config.yaml:ro
- ./secrets/api-key.txt:/run/secrets/api-key:ro
Config dan secret di-mount read-only. Source of truth di host, container tidak boleh mengubah.
Pola 4: Test Runner #
services:
test:
image: my-app:test
volumes:
- ./src:/app/src:ro
- ./tests:/app/tests:ro
- test-results:/app/results
command: npm test
volumes:
test-results:
Source code dan test file di-bind mount read-only. Test results di volume untuk diambil setelah test selesai.
Pola 5: Debug dengan Volume Bersama #
# Container production
docker run -d --name app -v app-data:/app/data my-app:prod
# Container debug mount volume yang sama, read-only
docker run -it --rm \
--volumes-from app \
alpine sh
Container debug punya akses ke volume production (read-only) untuk inspect data tanpa mempengaruhi production.
Migrasi dari Bind Mount ke Volume #
Untuk transisi ke production, kamu bisa migrasi dari bind mount ke volume:
Cara 1: Pindah Manual #
# Stop container
docker stop app
# Copy data dari bind mount ke volume
docker run --rm \
-v /home/dev/project/data:/source:ro \
-v app-data:/target \
alpine \
cp -a /source/. /target/
# Update compose untuk pakai volume
# volumes:
# - app-data:/app/data (bukan ./data:/app/data)
# Start container dengan volume
docker compose up -d
Cara 2: Pakai Volume sebagai Wrapper #
services:
app:
volumes:
- app-data:/app/data
volumes:
app-data:
driver: local
driver_opts:
type: none
o: bind
device: /var/lib/app-data
driver_opts: type=none, o=bind, device=... membuat volume yang sebenarnya adalah bind mount, tapi dengan API volume. Ini cara untuk transisi gradual.
Ringkasan #
- Bind mount = mount file atau direktori host langsung ke container. Tidak ada abstraksi, tidak ada copy data — yang ada adalah satu filesystem dilihat dari dua namespace.
- Cara kerja: kernel Linux mount namespace mengarahkan path di container ke path di host. Perubahan di host langsung terlihat di container, dan sebaliknya.
- Sintaks CLI:
-v host_path:container_path:optionsatau--mount type=bind,source=...,target=.... Opsi:rountuk read-only.- Sintaks Compose: path relatif terhadap file compose (
./src:/app/src), atau path absolut (/home/user/data:/data). Long syntax dengantype: bind, source: ..., target: ....- Use case utama: local development dengan live reload source code, akses file konfigurasi, hot reload framework (Nodemon, Vite, Air), sharing data dengan tool host.
- Keunggulan: akses langsung dari host, transparan, mendukung hot reload, mudah debug, ideal untuk development workflow.
- Risiko: keamanan (container bisa hapus file host), tidak portabel (path host harus konsisten), performa di macOS/Windows, permission issue, dan tidak scalable untuk multi-host.
- Anti-pattern: mount
/var/run/docker.sock(container escape), mount root filesystem, mount system paths, pakai bind mount untuk data production.- Perbedaan dengan volume: volume managed by Docker (portabel, aman, scalable), bind mount user-managed (transparan, powerful, risky). Untuk production, volume. Untuk development, bind mount.
- Best practice: hanya untuk development, hindari path kritis, pakai
:rountuk config, kombinasikan dengan volume untuk data penting, perhatikan permission, gunakan path relatif di Compose, dokumentasikan mount point.- Pola umum: development standar (source code bind mount + dependency volume + config ro), multi-service development, test runner, debug dengan volume bersama.
- Ingat prinsip: “Bind mount cocok untuk developer, Docker volume cocok untuk production.” Pahami kapan menggunakan masing-masing, dan jangan campur aduk tanpa alasan.
← Sebelumnya: Volume Lifecycle Berikutnya: Volume vs Bind Mount →