توی این درس یاد میگیری چرا دادههای داخل کانتینر با حذف آن از بین میروند و سه راه برای ماندگار کردنشان: 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 هم یک تختهی وایتبرد است: سریع، ولی با خروج پاک میشود.
سه راه ماندگاری
Section titled “سه راه ماندگاری”مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: بدون volume، داده میرود
Section titled “مثال ۱: بدون volume، داده میرود”docker run --name lx-v1 alpine sh -c 'echo "مهم" > /data.txt && cat /data.txt'docker rm lx-v1docker run --rm alpine sh -c 'cat /data.txt 2>&1'مهمlx-v1cat: can't open '/data.txt': No such file or directoryفایل داخل لایهی کانتینر اول بود؛ با docker rm رفت و کانتینر جدید آن را ندارد.
مثال ۲: named volume ماندگار است
Section titled “مثال ۲: named volume ماندگار است”docker volume create lx-datadocker run --rm -v lx-data:/data alpine sh -c 'echo "مهم" > /data/note.txt'docker run --rm -v lx-data:/data alpine cat /data/note.txtdocker volume ls --filter name=lx-datalx-dataمهمDRIVER VOLUME NAMElocal 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)”docker run -d --name lx-db -e MYSQL_ROOT_PASSWORD=secret -v lx-dbdata:/var/lib/mysql mysql:8.4 >/dev/nullfor i in $(seq 1 40); do docker logs lx-db 2>&1 | grep -q "ready for connections.*port: 3306" && break; sleep 2; donedocker 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 Warningdocker exec -e MYSQL_PWD=secret lx-db mysql -uroot -N -e "SELECT COUNT(*) FROM shop.items" 2>&1 | grep -v Warning1دادههای MySQL در /var/lib/mysql ذخیره میشوند؛ آن را به یک volume وصل کردیم. حالا کانتینر را حذف میکنیم و یکی تازه به همان volume وصل میکنیم:
docker rm -f lx-db >/dev/nulldocker run -d --name lx-db2 -e MYSQL_ROOT_PASSWORD=secret -v lx-dbdata:/var/lib/mysql mysql:8.4 >/dev/nullfor i in $(seq 1 40); do docker logs lx-db2 2>&1 | grep -q "ready for connections.*port: 3306" && break; sleep 2; donedocker exec -e MYSQL_PWD=secret lx-db2 mysql -uroot -N -e "SELECT id, name FROM shop.items" 2>&1 | grep -v Warning1 کتابسطر کتاب هنوز هست. کانتینر عوض شد، داده ماند. (متغیر -e MYSQL_ROOT_PASSWORD=secret را در درس بعد توضیح میدهیم.)
در -v lx-dbdata:/var/lib/mysql، سمت چپ دونقطه چیست؟
سمت چپ: منبع (volume یا مسیر میزبان)؛ سمت راست: مسیر داخل کانتینر.
مثال ۴: bind mount و ویرایش زنده
Section titled “مثال ۴: bind mount و ویرایش زنده”یک پوشهی سایت روی سیستم خودت میسازیم و آن را داخل nginx وصل میکنیم:
mkdir -p siteecho "<h1>نسخه ۱</h1>" > site/index.htmldocker run -d --name lx-site -p 8330:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:alpine >/dev/nullsleep 2curl -s localhost:8330<h1>نسخه ۱</h1>حالا بدون restart کردن کانتینر، فایل را روی میزبان عوض میکنیم:
echo "<h1>نسخه ۲ (ویرایششده روی میزبان)</h1>" > site/index.htmlcurl -s localhost:8330<h1>نسخه ۲ (ویتغییر فوراً دیده شد، چون پوشهی میزبان و پوشهی کانتینر یک محل واحد هستند. این روش توسعه است: کد را روی سیستم خودت با ویرایشگر مورد علاقهات مینویسی و داخل کانتینر اجرا میشود. ($(pwd) مسیر کامل پوشهی فعلی را میدهد؛ bind mount مسیر مطلق میخواهد.)
مثال ۵: حالت فقطخواندنی (:ro)
Section titled “مثال ۵: حالت فقطخواندنی (:ro)”گزینهی :ro در آخر -v کانتینر را از نوشتن روی آن محدود کرد. ببین:
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 داده را فقط در حافظه نگه میدارد (سریع، موقت، و هرگز روی دیسک نمیرود):
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 بگیرد:
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.tgzdata.tgz 139 بایت././note.txtپشت پرده
Section titled “پشت پرده”volume کجاست؟
Section titled “volume کجاست؟”docker volume inspect lx-data --format 'driver: {{.Driver}}mountpoint: {{.Mountpoint}}'driver: localmountpoint: /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 مستقیم فایلسیستم میزبان را وصل میکنند: سریعتر و مستقل از عمر کانتینر.
initial copy
Section titled “initial copy”وقتی یک volume خالی را روی مسیری وصل میکنی که در image محتوا دارد، داکر محتوای image را داخل volume کپی میکند (فقط بار اول). ولی یک bind mount پوشهی داخل image را کاملاً میپوشاند (اشتباه ۱ پایین).
جدولهای مرجع
Section titled “جدولهای مرجع”| ویژگی | 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 نوشت (خواناتر و برای اسکریپتها بهتر).
اشتباهات رایج
Section titled “اشتباهات رایج”۱) bind mount پوشهی خالی، محتوای image را میپوشاند
Section titled “۱) bind mount پوشهی خالی، محتوای image را میپوشاند”mkdir -p empty-dirdocker run -d --name lx-ro -p 8331:80 -v "$(pwd)/empty-dir:/usr/share/nginx/html" nginx:alpine >/dev/nullsleep 2curl -s -o /dev/null -w "HTTP %{http_code}\n" localhost:8331docker rm -f lx-ro >/dev/nullHTTP 403فایلهای پیشفرض nginx داخل /usr/share/nginx/html بودند؛ bind mount پوشهی خالی میزبان آنها را پوشاند و جواب 403 شد. راهحل: یا فایلها را در پوشهی میزبان بگذار، یا از named volume استفاده کن (که محتوای image را کپی میکند).
۲) اسم نسبی، volume میسازد (نه bind mount)
Section titled “۲) اسم نسبی، volume میسازد (نه bind mount)”docker run --rm -v site:/data alpine truedocker 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”docker volume create lx-pv >/dev/nulldocker run --rm --user 1000 -v lx-pv:/data alpine sh -c 'touch /data/x 2>&1'docker volume rm lx-pvtouch: /data/x: Permission deniedlx-pvvolume ساختهشده توسط داکر مالک root دارد؛ کاربر 1000 اجازهی نوشتن ندارد. راهحل: مالکیت را با یک کانتینر کمکی عوض کن (chown)، یا در Dockerfile پوشه را با مالک درست بساز.
۴) حذف volume در حال استفاده
Section titled “۴) حذف volume در حال استفاده”docker run -d --name lx-v2 -v lx-data:/data alpine sleep 60 >/dev/nulldocker volume rm lx-datadocker rm -f lx-v2 >/dev/nullError response from daemon: remove lx-data: volume is in use - [920385ef2a5e0b73f81eae15ea2be9f75f5b79386678b568460d5fe9ec365d4b]تا کانتینری (حتی متوقفشده) از volume استفاده میکند، حذف نمیشود. راهحل: اول کانتینر را حذف کن، بعد volume.
یک volume به اسم lx-pv بساز، در فهرست volume ها نشانش بده و حذفش کن.
دیدن جواب
docker volume create lx-pvdocker volume ls --filter name=lx-pv --format '{{.Name}}'docker volume rm lx-pvlx-pvlx-pvlx-pvتمرین اصلی: یک سایت استاتیک را با bind mount سرو کن و زنده ویرایشش کن. پوشهی blog با یک index.html بساز، با nginx روی پورت 8332 سرو کن، سپس محتوا را عوض کن و ثابت کن بدون restart دیده میشود.
دیدن جواب
mkdir -p blog && echo "<p>پست ۱</p>" > blog/index.htmldocker run -d --name lx-ro -p 8332:80 -v "$(pwd)/blog:/usr/share/nginx/html:ro" nginx:alpine >/dev/null; sleep 2curl -s localhost:8332echo "<p>پست ۲</p>" > blog/index.htmlcurl -s localhost:8332docker rm -f lx-ro >/dev/null<p>پست ۱</p><p>پست ۲</p>در یک volume به اسم lx-pv یک فایل بساز، از آن با tar پشتیبان بگیر، volume را حذف کن، یک volume تازه بساز و پشتیبان را داخلش بازیابی کن. ثابت کن فایل برگشته.
دیدن جواب
docker volume create lx-pv >/dev/nulldocker 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/nulldocker volume create lx-pv >/dev/nulldocker run --rm -v lx-pv:/data -v "$(pwd)":/backup alpine tar xzf /backup/pv.tgz -C /datadocker run --rm -v lx-pv:/data alpine cat /data/f.txtdocker volume rm lx-pv >/dev/nullدادهی مهمآزمونک
Section titled “آزمونک”دادههای لایهی قابلنوشتن کانتینر با چه چیزی از بین میروند؟
با حذف کانتینر آن لایه از بین میرود؛ stop و start آن را نگه میدارند.
تفاوت اصلی named volume با bind mount؟
volume مستقل از ساختار پوشههای میزبان است؛ bind mount به مسیر میزبان وابسته است.
در -v site:/data، «site» چیست؟
برای پوشهی میزبان باید مسیر مطلق (مثل $(pwd)/site) بنویسی.
:ro در آخر -v چه میکند؟
مثلاً برای محافظت از کد منبع یا تنظیمات.
tmpfs برای چه مناسب است؟
tmpfs در RAM است و با توقف کانتینر پاک میشود.
جمعبندی
Section titled “جمعبندی”- دادهی داخل کانتینر با
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" IMG | bind mount (مسیر مطلق) |
docker run -v "$(pwd)/dir:/path:ro" IMG | bind mount فقطخواندنی |
docker run --tmpfs /path IMG | tmpfs در حافظه |
docker run --mount type=volume,src=N,dst=/p IMG | شکل بلند و خواناتر |
docker run -v data:/var/lib/mysql mysql | ماندگار کردن دادهی دیتابیس |