Docker Compose: Bind Mount vs Volume Bernama
Pahami perbedaan bind mount dan volume bernama di Docker Compose, termasuk jebakan izin file, cara inspeksi, pencadangan, dan migrasi data dengan aman.
Bind mount atau volume bernama: 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 volume bernama untuk data yang dimiliki aplikasi, seperti file basis data, indeks pencarian, dan media yang diunggah. Bind mount menunjuk ke path pada host yang dapat Anda buka di editor. Volume bernama adalah penyimpanan yang dibuat dan dilacak Docker untuk Anda, dan Anda mengaksesnya melalui Docker.
Keduanya muncul di bawah kunci volumes: yang sama di dalam service, sehingga 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 adalah nama, sehingga merupakan volume bernama, dan nama itu juga harus dideklarasikan di blok volumes: tingkat teratas.
Dua sintaksis 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 sebagai hanya-baca. Ini adalah nilai default yang tepat untuk konfigurasi yang tidak boleh ditulis ulang oleh container. Jika entri volumes: tingkat atas diabaikan, Compose berhenti dengan service "db" refers to undefined volume pgdata.
Jalankan dan tampilkan daftar yang dibuat Docker:
docker compose up -d
docker volume lsNama volume tersebut bukan pgdata. Namanya <project>_pgdata, karena nama proyek secara default mengikuti nama direktori yang berisi file compose. Direktori bernama myapp menghasilkan myapp_pgdata. Hal ini penting karena mengganti nama direktori akan membuat volume kosong baru, sehingga aplikasi terlihat seperti kehilangan datanya. Data tersebut tidak hilang: volume lama masih tercantum dalam docker volume ls. Tetapkan namanya dengan name: di file compose, atau tetapkan COMPOSE_PROJECT_NAME jika direktori mungkin dipindahkan. Pengaturan seperti ini sebaiknya disimpan bersama file lingkungan dan secret Compose lainnya.
Mengapa kesalahan izin hanya terjadi pada bind mount
Inilah perbedaan praktis terbesar. Perbedaan ini berasal dari satu aturan: named volume yang masih kosong saat pertama kali digunakan diisi dari image, sedangkan bind mount tidak.
Saat Docker memasang named volume kosong di atas direktori yang sudah berisi konten dalam image, Docker menyalin konten tersebut ke dalam volume, beserta kepemilikan dan mode yang ditetapkan image. Image Postgres resmi merilis /var/lib/postgresql/data yang dimiliki oleh user postgres miliknya sendiri. Karena itu, volume memiliki numeric id yang sama, dan database dapat dimulai.
Bind mount bekerja sebaliknya. Apa pun yang ada di host akan terlihat oleh container, termasuk kepemilikannya, sedangkan konten image pada path tersebut tersembunyi. Jika direktori pada host tidak ada, Docker daemon membuatnya. Daemon berjalan sebagai root, sehingga direktori tersebut dimiliki oleh root:root. Proses container yang berjalan sebagai user non-root kemudian tidak dapat menulis ke direktori itu:
PermissionError: [Errno 13] Permission denied: '/data/app.db'Perbaikannya adalah menyamakan angkanya. Kepemilikan 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 mencetak uid yang benar-benar digunakan oleh proses container. Samakan direktori pada host dengan angka tersebut, atau tetapkan container agar menggunakan angka Anda dengan user: "1000:1000" di dalam 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 memulai entrypoint sebagai root, menurunkan hak akses, lalu mengharapkan kepemilikan tertentu pada direktori di bawahnya.
Ada dua hal lain yang perlu diketahui. Pada Fedora, RHEL, dan sistem lain dengan SELinux (security-enhanced Linux) dalam mode enforcing, bind mount ditolak sampai diberi relabel. Karena itu, tambahkan :z untuk path yang dibagikan antar-container, atau :Z untuk path yang hanya boleh digunakan oleh satu container, dengan sintaks - ./data:/data:Z. Selain itu, bind mount untuk satu file, bukan direktori, dapat bermasalah saat editor mengganti file tersebut, bukan menulis langsung ke file itu. Hal ini terjadi karena mount mengikuti inode asli. Container terus melihat konten lama sampai Anda memulai ulang container. Gunakan mount pada direktori induk jika file sering diedit.
Performa: kapan perbedaannya nyata
Pada server Linux, kedua jenis volume melewati jalur kernel yang sama. Karena itu, perbedaan throughput cukup kecil dan Anda tidak perlu memilih berdasarkan faktor tersebut. Volume bernama yang menggunakan driver default local berada di filesystem yang sama dengan bagian Docker lainnya, di bawah /var/lib/docker/volumes/. Bind mount berada di lokasi yang Anda tentukan.
Perbedaannya terlihat pada Docker Desktop untuk macOS dan Windows, karena container berjalan di dalam mesin virtual. Bind mount di sana melewati filesystem host menuju mesin virtual tersebut melalui lapisan berbagi file. Beban kerja dengan banyak operasi file kecil, seperti pohon dependensi Node.js atau cache framework PHP, menjadi jauh lebih lambat. Volume bernama tetap berada di dalam mesin virtual dan tidak menanggung biaya tersebut. Karena itu, banyak file Compose untuk pengembangan melakukan bind mount pada direktori sumber, tetapi mendeklarasikan volume bernama di atas node_modules.
Perbedaan nyata lainnya adalah lokasi penyimpanan byte. Bind mount ke /mnt/backup menyimpan data pada disk tersebut. Volume bernama menyimpan data pada filesystem yang memuat /var/lib/docker, yang pada VPS biasanya merupakan disk root. Database yang terus bertambah di dalam volume bernama akan memenuhi disk yang sama dengan disk tempat log sistem disimpan. Periksa sebelum hal ini menjadi insiden:
docker system df -v
df -h /var/lib/dockerdocker system df -v mencantumkan semua volume beserta ukurannya dan menandai volume yang tidak lagi digunakan oleh container mana pun.
Memeriksa volume bernama
Volume bernama bukan kotak hitam. Tanyakan lokasi volume 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 ini berguna untuk pemeriksaan cepat. Jangan menganggapnya sebagai tempat untuk mengedit file. Menulis di sana sebagai root akan menimbulkan kembali masalah kepemilikan yang dijelaskan di atas. Path tersebut juga merupakan detail driver local yang tidak digunakan oleh driver volume lain.
Cara yang 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 dengan driver apa pun, melihat izin yang sama seperti yang dilihat container sebenarnya, dan tidak meninggalkan apa pun karena --rm.
Mencadangkan setiap jenis
Bind mount adalah direktori biasa, sehingga alat pencadangan tingkat file dapat menanganinya secara langsung. Arahkan pencadangan ke path host, dan proses selesai. Named volume memerlukan satu langkah tambahan karena alat tersebut harus mengakses isinya. Mount volume dan direktori host ke dalam container berumur singkat 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 sedang berjalan akan menghasilkan arsip dari kondisi yang terus berubah. Arsip tersebut dapat dipulihkan ke keadaan yang rusak. Hentikan layanan terlebih dahulu, atau lakukan dump menggunakan alat milik 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 biasa bersama file compose.
Memigrasikan bind mount ke volume bernama
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 kepemilikan, mode, dan stempel waktu. Dengan demikian, pengguna container yang dapat membaca direktori lama tetap dapat membaca volume baru. Kemudian ubah layanan agar menggunakan pgdata:/var/lib/postgresql/data, tambahkan pgdata ke blok tingkat teratas volumes:, jalankan docker compose up -d, lalu baca log aplikasi sebelum menghapus direktori lama. Untuk arah sebaliknya, gunakan perintah yang sama dengan menukar /from dan /to.
Perhatikan satu hal saat melakukan pengujian. docker compose down membiarkan volume bernama tetap ada, sedangkan docker compose down -v menghapus semua volume bernama yang dideklarasikan oleh proyek, dan tindakan ini tidak dapat dibatalkan. Bind mount tetap ada setelah kedua perintah tersebut karena Docker tidak pernah memiliki direktori itu. Jika perintah siklus hidup ini masih baru bagi Anda, panduan dasar Docker Compose untuk VPS menjelaskannya secara bertahap.
Memilih berdasarkan layanan
Tentukan siapa yang menulis file tersebut. Konfigurasi yang Anda edit di editor teks dan commit ke git harus berada dalam bind mount yang dipasang pada :ro, karena Anda ingin konfigurasi tersebut terlihat dan memiliki versi yang tercatat. Status aplikasi yang tidak pernah Anda buka secara manual harus berada dalam named volume, karena Docker menetapkan izin dengan benar dan data tidak bergantung pada path di host.
Kasus campurannya adalah media. Pustaka foto ditulis oleh aplikasi, tetapi juga dikelola oleh Anda, dan sering kali cukup besar sehingga memerlukan disk tertentu. Pasang pustaka tersebut dengan bind mount ke path pada disk itu, lalu tetapkan kepemilikannya secara sengaja satu kali. Itulah pola yang biasanya digunakan oleh stack yang di-host sendiri: named volume untuk database dan cache, bind mount untuk konfigurasi dan direktori besar yang penting bagi Anda.
FAQ
Apa perbedaan antara bind mount dan named volume?
Bind mount memetakan path pada host ke dalam container. Kedua sisi melihat direktori yang sama, sehingga Anda dapat mengeditnya dengan alat biasa. Named volume adalah penyimpanan yang dibuat dan dikelola Docker, dirujuk berdasarkan nama, dan dideklarasikan dalam blok volumes: tingkat teratas. Perbedaan praktisnya adalah kepemilikan: gunakan bind mount untuk konfigurasi yang Anda kelola, dan named volume untuk data yang dikelola aplikasi.
Mengapa saya mendapatkan "permission denied" dengan bind mount, tetapi tidak dengan named volume?
Named volume kosong diisi dari image. Karena itu, volume tersebut mewarisi kepemilikan yang ditetapkan image, sehingga user dalam container dapat menulis ke dalamnya. Bind mount menampilkan direktori host persis seperti kondisinya. Jika Docker harus membuat direktori tersebut, Docker membuatnya dengan root sebagai pemilik. Jalankan docker compose exec <service> id untuk melihat id numerik yang digunakan container, lalu sudo chown -R <uid>:<gid> pada direktori host, atau tetapkan user: "1000:1000" pada service.
Di mana Docker menyimpan named volume di disk?
Dengan driver local default, volume tersebut berada di bawah /var/lib/docker/volumes/<volume>/_data, dan docker volume inspect <volume> mencetak Mountpoint yang tepat. Baca nilainya jika perlu memeriksa sesuatu, tetapi tulis 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. Kemudian, arsipkan data dari satu lokasi ke lokasi lain menggunakan docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Untuk database, lakukan dump dengan alat bawaan database tersebut, bukan dengan menyalin file yang sedang digunakan. Penyalinan file saat proses penulisan berlangsung dapat menyebabkan hasil pemulihan berada 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 setiap named volume yang dideklarasikan project secara permanen. Bind mount tidak pernah dihapus oleh kedua perintah tersebut, karena direktori itu milik host, bukan Docker.