Docker Compose: Bind Mount vs Named Volume
Ketahui perbezaan antara bind mount dan named volume dalam Docker Compose. Panduan ini menjelaskan cara mengurus fail konfigurasi, data aplikasi, serta isu keizinan fail.
Bind mount atau named volume: jawapan ringkas
Docker Compose mempunyai dua jenis volume, dan pemilihannya bergantung kepada siapa yang memiliki fail tersebut. Gunakan bind mount untuk fail yang anda tulis dan baca sendiri, seperti konfigurasi, templat, dan laman web statik. Gunakan named volume untuk data yang dimiliki oleh aplikasi, seperti fail pangkalan data, indeks carian, dan media yang dimuat naik. Bind mount merujuk kepada laluan pada hos yang boleh anda buka dalam editor. Named volume pula ialah storan yang dicipta dan dijejak oleh Docker untuk anda, dan anda mengaksesnya melalui Docker.
Kedua-duanya muncul di bawah kunci volumes: yang sama di dalam servis, itulah sebabnya ia sering tertukar. Perbezaannya terletak pada bahagian kiri tanda titik bertindih. Bahagian kiri yang bermula dengan . atau / ialah laluan hos, maka ia adalah bind mount. Sebarang input lain ialah nama, maka ia adalah named volume, dan nama tersebut mesti diisytiharkan dalam blok volumes: peringkat atas.
Dua sintaks dalam fail 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 ialah volum bernama. ./nginx.conf:/etc/nginx/nginx.conf ialah bind mount, dan :ro melekapkannya sebagai baca sahaja, yang merupakan tetapan lalai yang betul untuk konfigurasi yang tidak sepatutnya diubah suai oleh kontena. Abaikan entri volumes: di peringkat atas dan Compose akan berhenti dengan service "db" refers to undefined volume pgdata.
Jalankan servis tersebut dan senaraikan apa yang telah dicipta oleh Docker:
docker compose up -d
docker volume lsVolum tersebut tidak dinamakan pgdata. Ia dinamakan <project>_pgdata, di mana nama projek secara lalai mengambil nama direktori yang menyimpan fail compose tersebut. Direktori bernama myapp akan menghasilkan myapp_pgdata. Perkara ini penting kerana menamakan semula direktori akan memberikan anda volum baharu yang kosong, dan aplikasi akan kelihatan seolah-olah kehilangan datanya. Ia tidak hilang: volum lama masih disenaraikan oleh docker volume ls. Tetapkan nama secara khusus dengan name: dalam fail compose, atau tetapkan COMPOSE_PROJECT_NAME, jika direktori tersebut mungkin dipindahkan. Tetapan seperti itu sepatutnya diletakkan bersama fail persekitaran dan rahsia Compose anda yang lain.
Mengapa ralat keizinan sering berlaku pada bind mount
Ini adalah perbezaan praktikal yang paling besar, dan ia berpunca daripada satu peraturan: named volume yang kosong pada penggunaan pertama akan diisi (seeded) daripada imej, manakala bind mount tidak akan diisi sedemikian.
Apabila Docker melekapkan (mount) named volume yang kosong pada direktori yang sudah mempunyai kandungan dalam imej, ia menyalin kandungan tersebut ke dalam volume, dengan pemilikan dan mod yang ditetapkan oleh imej. Imej rasmi Postgres menghantar /var/lib/postgresql/data yang dimiliki oleh pengguna postgres miliknya sendiri, jadi volume tersebut akan dimiliki oleh id angka yang sama, dan pangkalan data akan bermula.
Proses pengisian (seeding) itu adalah satu-satunya kelebihan yang dimiliki oleh named volume. Docker menguruskan lokasi volume tersebut, tetapi kontena membaca dan menulisnya dengan operasi sistem fail biasa, jadi pemilikan dan mod dikuatkuasakan sama seperti pada bind mount. Named volume yang dilekapkan pada laluan yang tidak dimiliki oleh imej, atau yang diisi buat kali pertama oleh imej atau pengguna yang berbeza, tidak mendapat pemilikan daripada imej, dan proses bukan root juga boleh dinafikan akses di situ. Semakan id di bawah berfungsi untuk kedua-dua jenis.
Bind mount melakukan perkara sebaliknya. Apa sahaja yang ada pada hos adalah apa yang dilihat oleh kontena, termasuk pemilikan, dan kandungan imej pada laluan tersebut disembunyikan. Jika direktori hos tidak wujud, daemon Docker akan menciptanya, dan daemon tersebut berjalan sebagai root, jadi anda akan mendapat direktori yang dimiliki oleh root:root. Proses kontena yang berjalan sebagai pengguna bukan root kemudiannya tidak boleh menulis ke direktori tersebut:
PermissionError: [Errno 13] Permission denied: '/data/app.db'Penyelesaiannya adalah dengan menyelaraskan nombor tersebut. Pemilikan merentasi bind mount dibandingkan mengikut id pengguna angka, bukan mengikut nama, kerana kontena mempunyai /etc/passwd sendiri. Pengguna bernama app di dalam kontena tidak membawa sebarang makna pada hos. Uid 1000 bermaksud uid 1000 pada kedua-dua belah pihak, kecuali daemon berjalan dengan pemetaan semula ruang nama pengguna (user-namespace remapping) (userns-remap, ditetapkan dalam /etc/docker/daemon.json atau pada baris arahan dockerd). Pemetaan semula mengalihkan setiap id kontena ke dalam julat bawahan pada hos. Dalam julat contoh Docker yang bermula pada 231072, uid 0 kontena adalah uid 231072 hos, jadi uid 1000 kontena adalah uid 232072 hos.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id mencetak uid yang digunakan oleh proses kontena semasa ia berjalan. Tanpa pemetaan semula, padankan direktori hos dengan nombor tersebut, atau tetapkan kontena kepada nombor anda dengan user: "1000:1000" dalam servis. Dengan pemetaan semula diaktifkan, user: tidak boleh menetapkan kontena kepada nombor hos anda, kerana id yang anda tetapkan juga dialihkan: user: "1000:1000" masih berakhir pada uid 232072 hos dalam julat contoh tersebut. Lakukan chown pada direktori hos kepada id hos yang dipetakan (julat anda sendiri berada dalam /etc/subuid dan /etc/subgid). Dokumentasi Docker hanya menamakan --userns=host pada docker container create, run atau exec sebagai cara untuk mematikan pemetaan semula bagi satu kontena sahaja. Menetapkan user: adalah lebih bersih untuk aplikasi yang anda tulis sendiri. Melakukan chown pada direktori hos adalah lebih selamat untuk imej yang anda tidak tulis, kerana sesetengah imej memulakan entrypoint sebagai root, menggugurkan keistimewaan, dan menjangkakan pemilikan tertentu di bawahnya.
Dua lagi perangkap yang perlu diketahui. Pada Fedora, RHEL dan sistem lain dengan SELinux (security-enhanced Linux) yang dikuatkuasakan, bind mount akan dinafikan sehingga ia dilabel semula, jadi tambahkan :z untuk laluan yang dikongsi antara kontena atau :Z untuk laluan yang hanya boleh digunakan oleh satu kontena, ditulis sebagai - ./data:/data:Z. Selain itu, bind mount bagi satu fail, bukannya direktori, akan terputus apabila editor menggantikan fail tersebut dan bukannya menulis di tempat asal, kerana mount tersebut mengikut inode asal. Kontena akan terus melihat kandungan lama sehingga anda memulakan semula kontena tersebut. Lekapkan direktori induk jika fail tersebut sering disunting.
Prestasi: di mana jurang tersebut nyata
Pada pelayan Linux, kedua-dua jenis storan melalui laluan kernel yang sama, jadi perbezaan daya pemprosesan (throughput) adalah cukup kecil sehingga anda tidak perlu membuat pilihan berdasarkan faktor tersebut. Named volumes yang menggunakan pemacu local lalai berada pada sistem fail yang sama dengan bahagian Docker yang lain, di bawah /var/lib/docker/volumes/, manakala bind mount pula berada di mana-mana lokasi yang anda tetapkan.
Jurang prestasi muncul pada Docker Desktop untuk macOS dan Windows, di mana kontena berjalan di dalam mesin maya. Bind mount di sana merentas dari sistem fail hos ke dalam mesin maya tersebut melalui lapisan perkongsian fail, dan beban kerja yang melibatkan banyak operasi fail kecil, seperti pokok dependensi Node.js atau cache rangka kerja PHP, akan menjadi perlahan dengan ketara. Named volumes kekal di dalam mesin maya dan tidak menanggung kos tersebut. Inilah sebabnya banyak fail compose pembangunan melakukan bind-mount pada direktori sumber tetapi mengisytiharkan named volume pada node_modules.
Perbezaan sebenar yang lain ialah di mana bait data disimpan. Bind mount ke /mnt/backup meletakkan data pada cakera tersebut. Named volume pula akan berada pada mana-mana sistem fail yang menempatkan /var/lib/docker, yang pada VPS biasanya merupakan cakera root. Pangkalan data yang membesar di dalam named volume akan memenuhi cakera yang sama dengan tempat log sistem anda disimpan. Periksa perkara ini sebelum ia menjadi insiden:
docker system df -v
df -h /var/lib/dockerdocker system df -v menyenaraikan setiap volume beserta saiznya, dan menandakan volume yang tidak lagi dirujuk oleh mana-mana kontena.
Memeriksa named volume
Named volume bukanlah kotak hitam. Tanya Docker di mana lokasinya:
docker volume inspect myapp_pgdataMedan Mountpoint memberikan laluan hos sebenar, biasanya /var/lib/docker/volumes/myapp_pgdata/_data. Anda boleh membacanya dengan sudo ls, dan ini berguna untuk pemeriksaan pantas. Jangan anggap ia sebagai tempat untuk menyunting fail. Menulis di sana sebagai root akan mewujudkan semula masalah pemilikan yang diterangkan di atas, dan laluan tersebut merupakan perincian pemacu local yang tidak dikongsi oleh pemacu volume lain.
Cara selamat untuk melihat bahagian dalam adalah dengan menggunakan container sementara yang melekapkan (mount) volume tersebut:
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volCara ini berkesan untuk mana-mana pemacu, melihat keizinan yang sama seperti yang dilihat oleh container sebenar, dan tidak meninggalkan sebarang kesan kerana --rm.
Sandaran bagi setiap jenis
Bind mount merupakan direktori biasa, jadi mana-mana alat sandaran peringkat fail boleh mengendalikannya. Halakan sandaran tersebut ke laluan hos dan proses selesai. Named volume memerlukan satu langkah tambahan kerana alat tersebut perlu mengakses bahagian dalamannya. Mount volume dan direktori hos ke dalam bekas (container) jangka pendek yang sama, kemudian tulis arkib:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Lakukan pemulihan dengan menterbalikkan proses tersebut ke dalam volume baharu:
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 mengekalkan pemilikan numerik apabila ia dijalankan sebagai root di dalam bekas, yang memastikan volume yang dipulihkan boleh digunakan oleh aplikasi.
Satu amaran terpakai untuk kedua-dua jenis tersebut. Menyalin fail pangkalan data semasa pangkalan data sedang berjalan akan menghasilkan arkib data yang berubah-ubah, dan ia boleh menyebabkan keadaan rosak semasa pemulihan. Hentikan servis terlebih dahulu, atau lakukan dump melalui alat pangkalan data itu sendiri, seperti dalam docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Tindakan itu menghasilkan fail biasa yang kemudiannya boleh anda sertakan dalam rutin sandaran restic yang disulitkan bersama-sama fail compose anda.
Memindahkan bind mount ke named volume
Proses ini merupakan penyalinan, bukan penamaan semula, dan mengambil masa kira-kira satu minit.
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 mengekalkan pemilikan, mod dan cap masa, supaya pengguna kontena yang boleh membaca direktori lama masih boleh membaca volum baharu tersebut. Kemudian, ubah servis untuk menggunakan pgdata:/var/lib/postgresql/data, tambahkan pgdata pada blok volumes: peringkat atas, jalankan docker compose up -d, dan baca log aplikasi sebelum anda memadam direktori lama. Proses sebaliknya menggunakan arahan yang sama dengan /from dan /to ditukar ganti.
Ingat satu perkara semasa anda membuat ujian. docker compose down tidak menjejaskan named volume, tetapi docker compose down -v memadamkan setiap named volume yang diisytiharkan oleh projek, dan tindakan ini tidak boleh dibatalkan. Bind mount terselamat daripada kedua-duanya, kerana Docker tidak pernah memiliki direktori tersebut. Jika arahan kitaran hayat masih baharu bagi anda, panduan asas Docker Compose untuk VPS menerangkan perkara ini dengan lebih lanjut.
Memilih, servis demi servis
Tentukan siapa yang menulis fail tersebut. Konfigurasi yang anda sunting dalam penyunting teks dan lakukan commit ke git perlu diletakkan dalam bind mount, yang dipasang pada :ro, kerana anda mahukannya boleh dilihat dan mempunyai versi. Keadaan aplikasi (application state) yang tidak pernah anda buka secara manual perlu diletakkan dalam named volume, kerana Docker menetapkan keizinan (permissions) daripada imej dan data tersebut tidak bergantung pada laluan hos.
Kes campuran adalah media. Pustaka foto ditulis oleh aplikasi tetapi juga diuruskan oleh anda, dan saiznya sering kali cukup besar sehingga memerlukan cakera khusus. Gunakan bind mount ke laluan pada cakera tersebut, dan tetapkan pemilikan (ownership) secara sengaja sekali sahaja. Itulah corak yang digunakan oleh kebanyakan stack self-hosted: named volume untuk pangkalan data dan cache, bind mount untuk konfigurasi dan direktori besar yang penting bagi anda. Meja bantuan seperti Chatwoot yang berjalan pada VPS berakhir tepat pada konfigurasi tersebut, dengan Postgres dalam named volume dan lampiran yang dimuat naik pada laluan yang boleh anda halakan untuk sandaran (backup).
FAQ
Apakah perbezaan antara bind mount dan named volume?
Bind mount memetakan laluan pada hos ke dalam kontena, supaya kedua-dua belah pihak melihat direktori yang sama dan anda boleh menyuntingnya dengan alatan biasa. Named volume adalah storan yang dicipta dan diuruskan oleh Docker, dirujuk mengikut nama dan diisytiharkan dalam blok volumes: peringkat atas. Perbezaan praktikalnya adalah pemilikan: bind mount untuk konfigurasi yang anda selenggara, named volume untuk data yang diselenggara oleh aplikasi.
Mengapa saya mendapat "permission denied" dengan bind mount tetapi tidak dengan named volume?
Named volume yang kosong diisi daripada imej, jadi ia biasanya mewarisi pemilikan yang ditetapkan oleh imej dan pengguna kontena boleh menulis kepadanya. Ia masih menguatkuasakan keizinan, jadi ia boleh gagal dengan cara yang sama jika imej tidak mempunyai apa-apa pada laluan tersebut. Bind mount menunjukkan direktori hos tepat seperti yang ada, dan jika Docker terpaksa mencipta direktori tersebut, ia menjadikannya dimiliki oleh root. Jalankan docker compose exec <service> id untuk melihat id angka yang digunakan oleh kontena, kemudian sudo chown -R <uid>:<gid> direktori hos, atau, apabila daemon tidak memetakan semula ruang nama pengguna, tetapkan user: "1000:1000" pada servis. Jika daemon menggunakan pemetaan semula ruang nama pengguna, lakukan chown kepada id hos yang dipetakan, kerana id kontena dialihkan pada hos dan user: tidak mengubah perkara tersebut.
Di manakah Docker menyimpan named volume pada cakera?
Dengan pemacu local lalai, ia disimpan di bawah /var/lib/docker/volumes/<volume>/_data, dan docker volume inspect <volume> mencetak Mountpoint yang tepat. Baca lokasi tersebut jika anda perlu menyemak sesuatu, tetapi tulis kepadanya hanya melalui kontena, kerana menyunting sebagai root pada hos akan mengubah pemilikan dengan cara yang tidak dijangka oleh kontena.
Bagaimanakah cara untuk membuat sandaran (backup) named volume?
Jalankan kontena jangka pendek dengan volume dan direktori hos kedua-duanya dilekapkan, kemudian arkibkan daripada satu ke yang lain dengan docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Untuk pangkalan data, lakukan dump dengan alat pangkalan data itu sendiri dan bukannya menyalin fail secara langsung, kerana salinan fail yang diambil semasa proses penulisan sedang berjalan boleh menyebabkan pemulihan dalam keadaan rosak.
Adakah docker compose down memadamkan volume saya?
docker compose down membuang kontena dan rangkaian serta membiarkan named volume di tempatnya. docker compose down -v juga memadamkan setiap named volume yang diisytiharkan oleh projek tersebut, secara kekal. Bind mount tidak pernah dibuang oleh mana-mana arahan, kerana direktori tersebut adalah milik hos dan bukan milik Docker.