رفتن به محتوا
LoopX

volume و bind mount

توی این درس یاد می‌گیری چرا داده‌های داخل کانتینر با حذف آن از بین می‌روند و سه راه برای ماندگار کردنشان: named volume (داکر خودش محل ذخیره را مدیریت می‌کند)، bind mount (یک پوشه‌ی مشخص از سیستم خودت را داخل کانتینر وصل می‌کنی) و tmpfs (داده‌ی موقتی در حافظه). با دستورهای docker volume create، docker volume ls، -v data:/var/lib/mysql و -v $(pwd):/usr/share/nginx/html کار می‌کنی و یک سایت استاتیک را زنده ویرایش می‌کنی.

تشبیه: کمد شخصی در برابر کمد مشترک

Section titled “تشبیه: کمد شخصی در برابر کمد مشترک”

کانتینر مثل یک اتاق هتل است: هر چه داخلش بگذاری، با تحویل اتاق (حذف کانتینر) پاک می‌شود. volume مثل صندوق امانات هتل است: مال تو است، جدا از اتاق نگهداری می‌شود و اتاق‌های بعدی هم می‌توانند به آن وصل شوند. bind mount مثل این است که در یک اتاق را به انبار خانه‌ات باز کنی: داخل هتل و بیرون آن هر دو یک محل را می‌بینند. tmpfs هم یک تخته‌ی وایت‌برد است: سریع، ولی با خروج پاک می‌شود.

named volume را داکر مدیریت می‌کند، bind mount یک پوشه‌ی دلخواه از میزبان است و tmpfs در حافظه‌ی RAM.

مثال ۱: بدون volume، داده می‌رود

Section titled “مثال ۱: بدون volume، داده می‌رود”
Terminal window
docker run --name lx-v1 alpine sh -c 'echo "مهم" > /data.txt && cat /data.txt'
docker rm lx-v1
docker run --rm alpine sh -c 'cat /data.txt 2>&1'
خروجی
مهم
lx-v1
cat: can't open '/data.txt': No such file or directory

فایل داخل لایه‌ی کانتینر اول بود؛ با docker rm رفت و کانتینر جدید آن را ندارد.

مثال ۲: named volume ماندگار است

Section titled “مثال ۲: named volume ماندگار است”
Terminal window
docker volume create lx-data
docker run --rm -v lx-data:/data alpine sh -c 'echo "مهم" > /data/note.txt'
docker run --rm -v lx-data:/data alpine cat /data/note.txt
docker volume ls --filter name=lx-data
خروجی
lx-data
مهم
DRIVER VOLUME NAME
local lx-data

دستور docker volume create یک volume به اسم lx-data می‌سازد. در -v lx-data:/data سمت چپ اسم volume و سمت راست مسیر داخل کانتینر است. کانتینر اول فایل را نوشت و پاک شد؛ کانتینر دوم (کاملاً جدا) همان فایل را می‌خواند. volume مستقل از هر کانتینر زندگی می‌کند. فهرست‌شان با docker volume ls است.

مثال ۳: دیتابیس واقعی با volume (-v data:/var/lib/mysql)

Section titled “مثال ۳: دیتابیس واقعی با volume (-v data:/var/lib/mysql)”
Terminal window
docker run -d --name lx-db -e MYSQL_ROOT_PASSWORD=secret -v lx-dbdata:/var/lib/mysql mysql:8.4 >/dev/null
for i in $(seq 1 40); do docker logs lx-db 2>&1 | grep -q "ready for connections.*port: 3306" && break; sleep 2; done
docker exec -e MYSQL_PWD=secret lx-db mysql -uroot -e "CREATE DATABASE shop; CREATE TABLE shop.items (id INT, name VARCHAR(20)); INSERT INTO shop.items VALUES (1,'کتاب');" 2>&1 | grep -v Warning
docker exec -e MYSQL_PWD=secret lx-db mysql -uroot -N -e "SELECT COUNT(*) FROM shop.items" 2>&1 | grep -v Warning
خروجی
1

داده‌های MySQL در /var/lib/mysql ذخیره می‌شوند؛ آن را به یک volume وصل کردیم. حالا کانتینر را حذف می‌کنیم و یکی تازه به همان volume وصل می‌کنیم:

Terminal window
docker rm -f lx-db >/dev/null
docker run -d --name lx-db2 -e MYSQL_ROOT_PASSWORD=secret -v lx-dbdata:/var/lib/mysql mysql:8.4 >/dev/null
for i in $(seq 1 40); do docker logs lx-db2 2>&1 | grep -q "ready for connections.*port: 3306" && break; sleep 2; done
docker exec -e MYSQL_PWD=secret lx-db2 mysql -uroot -N -e "SELECT id, name FROM shop.items" 2>&1 | grep -v Warning
خروجی
1 کتاب

سطر کتاب هنوز هست. کانتینر عوض شد، داده ماند. (متغیر -e MYSQL_ROOT_PASSWORD=secret را در درس بعد توضیح می‌دهیم.)

⚡ بررسی سریع

در -v lx-dbdata:/var/lib/mysql، سمت چپ دونقطه چیست؟

مثال ۴: bind mount و ویرایش زنده

Section titled “مثال ۴: bind mount و ویرایش زنده”

یک پوشه‌ی سایت روی سیستم خودت می‌سازیم و آن را داخل nginx وصل می‌کنیم:

Terminal window
mkdir -p site
echo "<h1>نسخه ۱</h1>" > site/index.html
docker run -d --name lx-site -p 8330:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:alpine >/dev/null
sleep 2
curl -s localhost:8330
خروجی
<h1>نسخه ۱</h1>

حالا بدون restart کردن کانتینر، فایل را روی میزبان عوض می‌کنیم:

Terminal window
echo "<h1>نسخه ۲ (ویرایش‌شده روی میزبان)</h1>" > site/index.html
curl -s localhost:8330
خروجی
<h1>نسخه ۲ (وی

تغییر فوراً دیده شد، چون پوشه‌ی میزبان و پوشه‌ی کانتینر یک محل واحد هستند. این روش توسعه است: کد را روی سیستم خودت با ویرایشگر مورد علاقه‌ات می‌نویسی و داخل کانتینر اجرا می‌شود. ($(pwd) مسیر کامل پوشه‌ی فعلی را می‌دهد؛ bind mount مسیر مطلق می‌خواهد.)

مثال ۵: حالت فقط‌خواندنی (:ro)

Section titled “مثال ۵: حالت فقط‌خواندنی (:ro)”

گزینه‌ی :ro در آخر -v کانتینر را از نوشتن روی آن محدود کرد. ببین:

Terminal window
docker exec lx-site sh -c 'touch /usr/share/nginx/html/x 2>&1'
خروجی
touch: /usr/share/nginx/html/x: Read-only file system

داخل کانتینر نمی‌توانی روی پوشه‌ی میزبان بنویسی، که برای محافظت از کد منبع یا تنظیمات عالی است.

مثال ۶: tmpfs و پشتیبان‌گیری از volume

Section titled “مثال ۶: tmpfs و پشتیبان‌گیری از volume”

tmpfs داده را فقط در حافظه نگه می‌دارد (سریع، موقت، و هرگز روی دیسک نمی‌رود):

Terminal window
docker run --rm --tmpfs /cache alpine sh -c 'echo x > /cache/f; mount | grep " /cache " | cut -d" " -f1,3,5'
خروجی
tmpfs /cache tmpfs

و برای پشتیبان‌گیری از یک volume، یک کانتینر موقت می‌سازی که هم volume و هم یک پوشه‌ی میزبان را ببیند و tar بگیرد:

Terminal window
docker run --rm -v lx-data:/data -v "$(pwd)":/backup alpine tar czf /backup/data.tgz -C /data .
ls -l data.tgz | awk '{print $NF, $5 " بایت"}'
tar tzf data.tgz
خروجی
data.tgz 139 بایت
./
./note.txt
Terminal window
docker volume inspect lx-data --format 'driver: {{.Driver}}
mountpoint: {{.Mountpoint}}'
خروجی
driver: local
mountpoint: /var/lib/docker/volumes/lx-data/_data

محل واقعی volume زیر /var/lib/docker/volumes/ است، داخل سیستم لینوکسی که daemon روی آن اجرا می‌شود (روی مک و ویندوز، داخل VM داکر؛ مستقیم قابل دسترسی نیست). مدیریتش از راه دستورهای docker volume است، نه دست‌کاری فایل‌ها.

چرا volume به‌جای لایه‌ی کانتینر؟

Section titled “چرا volume به‌جای لایه‌ی کانتینر؟”

داده‌ی لایه‌ی قابل‌نوشتن روی union filesystem است که برای نوشتن سنگین (دیتابیس) کند است و با حذف کانتینر می‌رود. volume و bind mount مستقیم فایل‌سیستم میزبان را وصل می‌کنند: سریع‌تر و مستقل از عمر کانتینر.

وقتی یک volume خالی را روی مسیری وصل می‌کنی که در image محتوا دارد، داکر محتوای image را داخل volume کپی می‌کند (فقط بار اول). ولی یک bind mount پوشه‌ی داخل image را کاملاً می‌پوشاند (اشتباه ۱ پایین).

ویژگی named volume bind mount tmpfs
محل ذخیره مدیریت داکر مسیر دلخواه میزبان حافظه‌ی RAM
ماندگاری بله بله (فایل‌های میزبان) خیر
شکل -v -v name:/path -v /abs/path:/path --tmpfs /path
مناسب دیتابیس، داده‌ی برنامه کد در توسعه، تنظیمات کش و رازهای موقت
پشتیبان‌گیری با کانتینر کمکی مستقیم از میزبان ندارد
دستور کار
docker volume create N ساخت
docker volume ls فهرست
docker volume inspect N جزئیات و Mountpoint
docker volume rm N حذف (اگر در استفاده نباشد)
docker volume prune حذف volume های بدون کانتینر (خطرناک!)

نوشتن بلند‌تر: به‌جای -v می‌توان --mount type=volume,src=lx-data,dst=/data نوشت (خواناتر و برای اسکریپت‌ها بهتر).

۱) bind mount پوشه‌ی خالی، محتوای image را می‌پوشاند

Section titled “۱) bind mount پوشه‌ی خالی، محتوای image را می‌پوشاند”
Terminal window
mkdir -p empty-dir
docker run -d --name lx-ro -p 8331:80 -v "$(pwd)/empty-dir:/usr/share/nginx/html" nginx:alpine >/dev/null
sleep 2
curl -s -o /dev/null -w "HTTP %{http_code}\n" localhost:8331
docker rm -f lx-ro >/dev/null
خروجی
HTTP 403

فایل‌های پیش‌فرض nginx داخل /usr/share/nginx/html بودند؛ bind mount پوشه‌ی خالی میزبان آن‌ها را پوشاند و جواب 403 شد. راه‌حل: یا فایل‌ها را در پوشه‌ی میزبان بگذار، یا از named volume استفاده کن (که محتوای image را کپی می‌کند).

۲) اسم نسبی، volume می‌سازد (نه bind mount)

Section titled “۲) اسم نسبی، volume می‌سازد (نه bind mount)”
Terminal window
docker run --rm -v site:/data alpine true
docker volume ls --filter name=site --format '{{.Name}}'
خروجی
site

-v site:/data یعنی «volume به اسم site»، نه پوشه‌ی site در مسیر فعلی. داکر بی‌صدا یک volume ساخت. راه‌حل: برای پوشه‌ی میزبان، مسیر مطلق بنویس ($(pwd)/site یا ./site در Compose) یا از --mount type=bind.

۳) نوشتن با کاربر غیر root روی volume

Section titled “۳) نوشتن با کاربر غیر root روی volume”
Terminal window
docker volume create lx-pv >/dev/null
docker run --rm --user 1000 -v lx-pv:/data alpine sh -c 'touch /data/x 2>&1'
docker volume rm lx-pv
خروجی
touch: /data/x: Permission denied
lx-pv

volume ساخته‌شده توسط داکر مالک root دارد؛ کاربر 1000 اجازه‌ی نوشتن ندارد. راه‌حل: مالکیت را با یک کانتینر کمکی عوض کن (chown)، یا در Dockerfile پوشه را با مالک درست بساز.

۴) حذف volume در حال استفاده

Section titled “۴) حذف volume در حال استفاده”
Terminal window
docker run -d --name lx-v2 -v lx-data:/data alpine sleep 60 >/dev/null
docker volume rm lx-data
docker rm -f lx-v2 >/dev/null
خروجی
Error response from daemon: remove lx-data: volume is in use - [920385ef2a5e0b73f81eae15ea2be9f75f5b79386678b568460d5fe9ec365d4b]

تا کانتینری (حتی متوقف‌شده) از volume استفاده می‌کند، حذف نمی‌شود. راه‌حل: اول کانتینر را حذف کن، بعد volume.

✎ تمرینآسان

یک volume به اسم lx-pv بساز، در فهرست volume ها نشانش بده و حذفش کن.

دیدن جواب
Terminal window
docker volume create lx-pv
docker volume ls --filter name=lx-pv --format '{{.Name}}'
docker volume rm lx-pv
خروجی
lx-pv
lx-pv
lx-pv
✎ تمرینمتوسط

تمرین اصلی: یک سایت استاتیک را با bind mount سرو کن و زنده ویرایشش کن. پوشه‌ی blog با یک index.html بساز، با nginx روی پورت 8332 سرو کن، سپس محتوا را عوض کن و ثابت کن بدون restart دیده می‌شود.

دیدن جواب
Terminal window
mkdir -p blog && echo "<p>پست ۱</p>" > blog/index.html
docker run -d --name lx-ro -p 8332:80 -v "$(pwd)/blog:/usr/share/nginx/html:ro" nginx:alpine >/dev/null; sleep 2
curl -s localhost:8332
echo "<p>پست ۲</p>" > blog/index.html
curl -s localhost:8332
docker rm -f lx-ro >/dev/null
خروجی
<p>پست ۱</p>
<p>پست ۲</p>
✎ تمرینسخت

در یک volume به اسم lx-pv یک فایل بساز، از آن با tar پشتیبان بگیر، volume را حذف کن، یک volume تازه بساز و پشتیبان را داخلش بازیابی کن. ثابت کن فایل برگشته.

دیدن جواب
Terminal window
docker volume create lx-pv >/dev/null
docker run --rm -v lx-pv:/data alpine sh -c 'echo "داده‌ی مهم" > /data/f.txt'
docker run --rm -v lx-pv:/data -v "$(pwd)":/backup alpine tar czf /backup/pv.tgz -C /data .
docker volume rm lx-pv >/dev/null
docker volume create lx-pv >/dev/null
docker run --rm -v lx-pv:/data -v "$(pwd)":/backup alpine tar xzf /backup/pv.tgz -C /data
docker run --rm -v lx-pv:/data alpine cat /data/f.txt
docker volume rm lx-pv >/dev/null
خروجی
داده‌ی مهم
؟ آزمونک
  1. داده‌های لایه‌ی قابل‌نوشتن کانتینر با چه چیزی از بین می‌روند؟

  2. تفاوت اصلی named volume با bind mount؟

  3. در -v site:/data، «site» چیست؟

  4. :ro در آخر -v چه می‌کند؟

  5. tmpfs برای چه مناسب است؟

  • داده‌ی داخل کانتینر با docker rm می‌رود؛ برای ماندگاری از volume یا bind mount استفاده کن.
  • named volume (-v name:/path) را داکر مدیریت می‌کند: مناسب دیتابیس. bind mount (-v /abs/path:/path) پوشه‌ی میزبان است: مناسب توسعه و کد زنده.
  • tmpfs داده‌ی موقتی در RAM است.
  • docker volume create/ls/inspect/rm را بلد باش؛ docker volume prune را با احتیاط.
  • bind mount پوشه‌ی داخل image را می‌پوشاند؛ volume خالی محتوای image را کپی می‌کند.
  • اسم بدون / یعنی volume، نه پوشه.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
docker volume create NAMEساخت volume
docker volume lsفهرست volume ها
docker volume inspect NAMEجزئیات و Mountpoint
docker volume rm NAMEحذف volume
docker run -v NAME:/path IMGاتصال named volume
docker run -v "$(pwd)/dir:/path" IMGbind mount (مسیر مطلق)
docker run -v "$(pwd)/dir:/path:ro" IMGbind mount فقط‌خواندنی
docker run --tmpfs /path IMGtmpfs در حافظه
docker run --mount type=volume,src=N,dst=/p IMGشکل بلند و خواناتر
docker run -v data:/var/lib/mysql mysqlماندگار کردن داده‌ی دیتابیس