Docker Compose: Pilih Bind Mount atau Named Volume
Pahami kapan memakai bind mount atau named volume untuk konfigurasi dan data, jebakan izin file, serta cara memeriksa, mencadangkan, dan memigrasikan volume.
Bind mount atau named volume: jawaban singkat
Volume Docker Compose memiliki dua jenis, dan pilihannya bergantung pada pihak yang memiliki file tersebut. Gunakan bind mount untuk file yang Anda tulis dan baca sendiri, seperti konfigurasi, template, dan situs statis. Gunakan named volume untuk data yang dikelola oleh aplikasi, seperti file database, indeks pencarian, dan media yang diunggah. Bind mount menunjuk ke path pada host yang dapat Anda buka di editor. Named volume adalah penyimpanan yang dibuat dan dilacak oleh Docker, dan Anda mengaksesnya melalui Docker.
Keduanya ditulis di bawah key volumes: yang sama di dalam sebuah service. Karena itu, keduanya sering tertukar. Perbedaannya terletak pada sisi kiri titik dua. Sisi kiri yang diawali . atau / adalah path host, sehingga merupakan bind mount. Selain itu, sisi kiri tersebut dianggap sebagai nama, sehingga merupakan named volume. Nama itu juga harus dideklarasikan di dalam blok volumes: tingkat atas.
Dua sintaks dalam file compose
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: nginx:1.27
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:pgdata:/var/lib/postgresql/data adalah volume bernama. ./nginx.conf:/etc/nginx/nginx.conf adalah bind mount, dan :ro memasangnya dalam mode hanya-baca. Ini merupakan default yang tepat untuk konfigurasi yang tidak boleh ditulis ulang oleh container. Jika entri tingkat teratas volumes: diabaikan, Compose berhenti dengan service "db" refers to undefined volume pgdata.
Jalankan compose dan tampilkan resource yang dibuat Docker:
docker compose up -d
docker volume lsNama volume tersebut bukan pgdata. Namanya adalah <project>_pgdata, karena nama project secara default mengikuti nama direktori yang berisi file compose. Direktori bernama myapp menghasilkan myapp_pgdata. Hal ini penting karena mengganti nama direktori akan menghasilkan volume kosong baru, sehingga aplikasi tampak kehilangan datanya. Data tersebut tidak hilang: volume lama masih ditampilkan oleh docker volume ls. Tetapkan namanya dengan name: di file compose, atau atur COMPOSE_PROJECT_NAME jika direktori mungkin dipindahkan. Pengaturan seperti ini sebaiknya ditempatkan bersama file environment dan secret Compose lainnya.
Mengapa error permission hanya terjadi pada bind mount
Inilah perbedaan praktis terbesar. Perbedaan ini berasal dari satu aturan: named volume yang masih kosong saat pertama kali digunakan akan diisi dari image, sedangkan bind mount tidak.
Saat Docker memasang named volume yang kosong pada direktori yang sudah berisi konten di dalam image, Docker menyalin konten tersebut ke dalam volume, termasuk ownership dan mode yang ditetapkan image. Official Postgres image menyediakan /var/lib/postgresql/data yang dimiliki oleh user postgres miliknya sendiri. Karena itu, volume menjadi dimiliki oleh id numerik yang sama, dan database dapat start.
Bind mount bekerja sebaliknya. Apa pun yang ada di host akan terlihat oleh container, termasuk ownership, sedangkan konten image pada path tersebut akan tersembunyi. Jika direktori pada host tidak ada, Docker daemon membuatnya. Daemon berjalan sebagai root, sehingga Anda mendapatkan direktori yang dimiliki oleh root:root. Proses container yang berjalan sebagai user non-root kemudian tidak dapat menulis ke direktori tersebut:
PermissionError: [Errno 13] Permission denied: '/data/app.db'Perbaikannya adalah menyamakan angkanya. Ownership pada bind mount dibandingkan berdasarkan user id numerik, bukan nama, karena container memiliki /etc/passwd sendiri. User bernama app di dalam container tidak memiliki arti di host. Uid 1000 berarti uid 1000 di kedua sisi.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id menampilkan uid yang sebenarnya digunakan oleh proses container. Samakan direktori pada host dengan angka tersebut, atau tetapkan container agar menggunakan uid Anda melalui user: "1000:1000" pada service. Menetapkan user: lebih rapi untuk aplikasi yang Anda tulis sendiri. Menggunakan chown pada direktori host lebih aman untuk image yang tidak Anda tulis, karena beberapa image menjalankan entrypoint sebagai root, menurunkan privilege, lalu mengharapkan ownership tertentu pada direktori di bawahnya.
Ada dua jebakan lain yang perlu diketahui. Pada Fedora, RHEL, dan sistem lain dengan SELinux (security-enhanced Linux) dalam mode enforcing, bind mount akan ditolak sampai diberi relabel. Karena itu, tambahkan :z untuk path yang digunakan bersama oleh beberapa container, atau :Z untuk path yang hanya boleh digunakan oleh satu container, dengan format - ./data:/data:Z. Selain itu, bind mount untuk satu file, bukan direktori, dapat bermasalah ketika editor mengganti file tersebut alih-alih menulis langsung ke file yang sama, karena mount mengikuti inode asli. Container akan terus melihat konten lama sampai Anda me-restart container. Pasang direktori induk jika file tersebut sering diedit.
Performa: perbedaan yang nyata
Pada server Linux, kedua jenis tersebut melewati jalur kernel yang sama. Karena itu, perbedaan throughput cukup kecil sehingga Anda tidak perlu memilih berdasarkan faktor tersebut. Named volume yang menggunakan driver local default berada pada filesystem yang sama dengan bagian Docker lainnya, di bawah /var/lib/docker/volumes/. Bind mount berada di lokasi yang Anda tentukan.
Perbedaan terlihat pada Docker Desktop untuk macOS dan Windows, karena container berjalan di dalam virtual machine. Bind mount di sana melintasi filesystem host menuju virtual machine melalui lapisan berbagi file. Beban kerja dengan banyak operasi file kecil, seperti dependency tree Node.js atau cache framework PHP, menjadi lebih lambat secara nyata. Named volume tetap berada di dalam virtual machine dan tidak menanggung biaya tersebut. Karena itu, banyak file Compose untuk pengembangan menggunakan bind mount pada direktori source, tetapi mendeklarasikan named volume pada node_modules.
Perbedaan nyata lainnya adalah lokasi penyimpanan byte. Bind mount ke /mnt/backup menyimpan data pada disk tersebut. Named volume disimpan pada filesystem yang memuat /var/lib/docker, yang pada VPS biasanya merupakan root disk. Database yang terus membesar di dalam named volume akan memenuhi disk yang sama dengan disk tempat log sistem disimpan. Periksa sebelum masalah terjadi:
docker system df -v
df -h /var/lib/dockerdocker system df -v mencantumkan setiap volume beserta ukurannya dan menandai volume yang tidak lagi digunakan oleh container mana pun.
Memeriksa named volume
Named volume bukan kotak hitam. Tanyakan lokasinya kepada Docker:
docker volume inspect myapp_pgdataKolom Mountpoint berisi path host yang sebenarnya, biasanya /var/lib/docker/volumes/myapp_pgdata/_data. Anda dapat membacanya dengan sudo ls, dan cara ini berguna untuk pemeriksaan cepat. Jangan menganggapnya sebagai lokasi untuk mengedit file. Menulis di sana sebagai root akan mengulangi masalah kepemilikan yang dijelaskan di atas, dan path tersebut merupakan detail driver local yang tidak dimiliki driver volume lainnya.
Cara aman untuk melihat isinya adalah menggunakan container sementara yang memasang volume tersebut:
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volCara ini berfungsi untuk driver apa pun, melihat permission yang sama seperti yang dilihat container sebenarnya, dan tidak meninggalkan apa pun karena --rm.
Mencadangkan setiap jenis
Bind mount adalah direktori biasa, sehingga tool pencadangan berbasis file sudah dapat menanganinya. Arahkan pencadangan ke path host, lalu selesai. Named volume memerlukan satu langkah tambahan karena tool tersebut harus mengakses isinya. Mount volume dan direktori host ke dalam container sementara yang sama, lalu buat arsip:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Pulihkan dengan membalik proses tersebut ke volume baru:
docker volume create myapp_pgdata_restored
docker run --rm \
-v myapp_pgdata_restored:/data \
-v "$PWD":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /datatar mempertahankan kepemilikan numerik saat dijalankan sebagai root di dalam container. Hal ini membuat volume yang dipulihkan tetap dapat digunakan oleh aplikasi.
Satu peringatan berlaku untuk kedua jenis tersebut. Menyalin file database saat database masih berjalan akan menghasilkan arsip dari data yang terus berubah. Arsip tersebut dapat dipulihkan dalam kondisi rusak. Hentikan service terlebih dahulu, atau buat dump menggunakan tool bawaan database, seperti pada docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Proses ini menghasilkan file biasa yang kemudian dapat Anda sertakan dalam rutinitas pencadangan restic terenkripsi bersama file compose.
Migrasi bind mount ke named volume
Pemindahan ini merupakan penyalinan, bukan penggantian nama, dan memerlukan waktu sekitar satu menit.
docker compose down
docker volume create myapp_pgdata
docker run --rm \
-v "$PWD/data":/from \
-v myapp_pgdata:/to \
alpine sh -c 'cp -a /from/. /to/'cp -a mempertahankan ownership, mode, dan timestamp, sehingga user container yang dapat membaca direktori lama tetap dapat membaca volume baru. Kemudian ubah service agar menggunakan pgdata:/var/lib/postgresql/data, tambahkan pgdata ke bagian tingkat teratas volumes:, jalankan docker compose up -d, lalu baca log aplikasi sebelum menghapus direktori lama. Untuk melakukan sebaliknya, gunakan perintah yang sama dengan menukar /from dan /to.
Ingat satu hal saat melakukan pengujian. docker compose down tidak mengubah named volume, tetapi docker compose down -v menghapus semua named volume yang dideklarasikan project, dan tindakan ini tidak dapat dibatalkan. Bind mount tetap ada setelah kedua perintah tersebut karena Docker tidak pernah memiliki direktori itu. Jika perintah untuk mengelola lifecycle masih baru bagi Anda, panduan dasar Docker Compose untuk VPS menjelaskannya secara bertahap.
Memilih, service demi service
Tanyakan siapa yang menulis file tersebut. Konfigurasi yang Anda edit di editor teks lalu commit ke git sebaiknya disimpan dalam bind mount yang di-mount :ro karena Anda ingin konfigurasi tersebut terlihat dan memiliki versi. Application state yang tidak pernah Anda buka secara manual sebaiknya disimpan dalam named volume karena Docker menetapkan permission dengan benar dan data tidak bergantung pada path di host.
Kasus campuran adalah media. Pustaka foto ditulis oleh aplikasi, tetapi juga dikelola oleh Anda, dan biasanya cukup besar sehingga memerlukan disk tertentu. Gunakan bind mount ke path pada disk tersebut, lalu tetapkan ownership secara sengaja satu kali. Inilah pola yang paling sering digunakan oleh stack self-hosted: named volume untuk database dan cache, serta bind mount untuk konfigurasi dan direktori besar yang penting bagi Anda. Support desk seperti Chatwoot yang berjalan di VPS tepat menggunakan pola ini, dengan Postgres dalam named volume dan lampiran yang diunggah pada path yang dapat Anda tentukan sebagai sumber backup.
FAQ
Apa perbedaan antara bind mount dan named volume?
Bind mount memetakan path pada host ke dalam container, sehingga kedua sisi melihat direktori yang sama dan Anda dapat mengeditnya dengan alat biasa. Named volume adalah penyimpanan yang dibuat dan dikelola oleh Docker, dirujuk berdasarkan nama, serta dideklarasikan dalam blok tingkat teratas volumes:. Perbedaan praktisnya adalah pengelolaan kepemilikan: gunakan bind mount untuk konfigurasi yang Anda kelola dan named volume untuk data yang dikelola aplikasi.
Mengapa saya mendapatkan "permission denied" pada bind mount, tetapi tidak pada named volume?
Named volume yang kosong diisi dari image, sehingga mewarisi kepemilikan yang ditetapkan image dan user di dalam container dapat menulis ke volume tersebut. Bind mount menampilkan direktori host persis seperti kondisinya. Jika Docker harus membuat direktori itu, Docker membuatnya dengan kepemilikan root. Jalankan docker compose exec <service> id untuk melihat id numerik yang digunakan container, lalu sudo chown -R <uid>:<gid> direktori host tersebut, atau tetapkan user: "1000:1000" pada service.
Di mana Docker menyimpan named volume pada disk?
Dengan driver local default, volume tersebut disimpan di bawah /var/lib/docker/volumes/<volume>/_data, dan docker volume inspect <volume> menampilkan Mountpoint secara persis. Baca path tersebut jika perlu memeriksa sesuatu, tetapi tulislah ke volume hanya melalui container. Mengeditnya sebagai root pada host dapat mengubah kepemilikan dengan cara yang tidak diharapkan container.
Bagaimana cara mencadangkan named volume?
Jalankan container berumur singkat dengan volume dan direktori host yang sama-sama di-mount, lalu arsipkan data dari satu lokasi ke lokasi lainnya dengan docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Untuk database, buat dump menggunakan alat milik database tersebut, bukan menyalin file yang sedang aktif. Penyalinan file saat proses penulisan berlangsung dapat menghasilkan pemulihan dalam kondisi rusak.
Apakah docker compose down menghapus volume saya?
docker compose down menghapus container dan network, tetapi membiarkan named volume tetap ada. docker compose down -v juga menghapus semua named volume yang dideklarasikan oleh project secara permanen. Bind mount tidak pernah dihapus oleh kedua perintah tersebut karena direktori itu milik host, bukan Docker.