DevOps

نصب داکر روی سرور ابری و اجرای اولین کانتینر

تصویر شاخص آموزش نصب داکر روی سرور مجازی: کارت ترمینال با دستورهای نصب موتور داکر، عبور از خطای 403 داکر هاب، بالا آوردن اپ چندسرویسه با داکر کامپوز و بستن پورت کانتینر روی لوکال‌هاست

نصب داکر روی سرور مجازی از آن کارهایی است که سه دستور بیشتر طول نمی‌کشد، اما تفاوتش با نصب درست در چیزهایی است که آن سه دستور نشان نمی‌دهند: پورت منتشرشده‌ی کانتینر از فایروال شما رد می‌شود، داکر هاب آی‌پی‌های ایران را با خطای 403 پس می‌زند، و لاگ کانتینرها هیچ سقف پیش‌فرضی ندارد. داکر اپ و همه‌ی وابستگی‌هایش را در یک بسته‌ی قابل حمل جمع می‌کند، و سرور ابری ساعتی بهترین زمین بازی برای آن است: محیط تمیز، تحویل معمولاً زیر یک دقیقه، و صورتحساب ساعتی که با حذف سرور می‌ایستد. اینجا از نصب تا Compose، فایروال، بکاپ و مهار دیسک را می‌رویم.

چرا داکر روی سرور ابری ساعتی جواب می‌دهد؟

  • محیط همیشه تمیز: سرور تازه می‌سازید، کانتینرها را بالا می‌آورید، تست می‌کنید و حذفش می‌کنید — فقط چند ساعت هزینه داده‌اید.
  • استقرار تکرارپذیر: ایمیج نسخه‌ی منجمدی از کل محیط است؛ همان چیزی که روی سرور تست اجرا شد بایت‌به‌بایت روی سرور اصلی بالا می‌آید و تفاوت نسخه‌ی PHP یا libc دو ماشین غافلگیرتان نمی‌کند.
  • چند اپ روی یک سرور: سایتی که به Node 22 نیاز دارد و رباتی با Node 24، بدون هیچ ترفندی کنار هم می‌نشینند.
  • برگشت‌پذیری: اگر نسخه‌ی جدید خراب بود ایمیج را به تگ قبلی برمی‌گردانید — برگرداندن یک نصب دستی سخت است، برگرداندن یک تگ یک خط.

در مقابل صادق باشیم: داکر لایه‌ای هم اضافه می‌کند و برای یک سایت وردپرسی ساده نصب مستقیم کم‌دردسرتر است؛ سودش وقتی است که چند سرویس دارید یا می‌خواهید همان چیزی که تست کرده‌اید منتشر شود.

قبل از نصب: سرور شما باید چه ویژگی‌هایی داشته باشد؟

نوع مجازی‌سازی مهم است. داکر برای ساختن کانتینر مستقیماً به namespaceها و cgroupهای هسته‌ی لینوکس دست می‌زند؛ روی مجازی‌سازی‌های اشتراکی‌هسته مثل OpenVZ این دسترسی بسته است و داکر یا بالا نمی‌آید یا عجیب رفتار می‌کند. سرور KVM هسته‌ی مستقل خودش را دارد؛ تفاوت دو معماری را در توضیح KVM و NVMe ببینید. سرور ابری مهران هاست KVM با دیسک NVMe است، پس این مانع را ندارید.

دیسک بیشتر از آنچه فکر می‌کنید لازم می‌شود. ایمیج پایه چند صد مگابایت، کش لایه‌های build چند گیگابایت و ولوم دیتابیس هرچه داده دارید — روی هم انباشته می‌شوند؛ برای محیطی چندسرویسه کمتر از ۲۰ گیگابایت دیسک واقع‌بینانه نیست.

معماری پردازنده را جدی بگیرید. ایمیجی که روی مک ARM ساخته شود، روی سرور x86 بلافاصله با exec format error می‌میرد. راه‌حل، ایمیج چندمعماری یا ساختن روی همان معماری مقصد است.

مخزن رسمی داکر امروز سه LTS اوبونتو را پشتیبانی می‌کند: ۲۶.۰۴، ۲۴.۰۴ و ۲۲.۰۴؛ برای سرور تازه دو تای اول را بردارید، چون پشتیبانی استاندارد ۲۲.۰۴ در ۲۰۲۷ تمام می‌شود و بعد از آن فقط با اشتراک Ubuntu Pro وصله‌ی امنیتی می‌گیرد.

نصب داکر روی اوبونتو

⚠️ روی سرور داخل ایران، قبل از اولین دستور: تحریم داکر فقط داکر هاب نیست. هم get.docker.com و هم مخزن download.docker.com روی همان CDN تحریم‌شده نشسته‌اند و به آی‌پی ایران خطای 403 با پیام «The Amazon CloudFront distribution is configured to block access from your country» می‌دهند؛ یعنی هنوز به docker pull نرسیده، همین گام نصب می‌ایستد. برخلاف رجیستری، این یکی با DNS رفع‌تحریم یا آینه‌ی apt داخلی رد می‌شود.

روش سریع: اسکریپت رسمی

سریع‌ترین راه، اسکریپت نصب خود داکر است:

curl -fsSL https://get.docker.com | sh

اسکریپت خودش سرویس را روی systemd راه می‌اندازد؛ systemctl status docker باید active نشان دهد. تست واقعی اجرای یک ایمیج است:

docker run hello-world

اگر پیام خوش‌آمد دیدید، موتور روشن است. روی سرور داخل ایران اما همین‌جا هم 403 می‌گیرید — این‌بار نصب سالم است و مشکل از رجیستریِ داکر هاب: راه‌حلش بخش بعد.

⚠️ مستندات رسمی داکر همین اسکریپت را برای محیط عملیاتی توصیه نمی‌کند: مخزن را طوری تنظیم می‌کند که هر اجرا آخرین نسخه را بگیرد. برای سرور تست عالی است؛ برای سایت واقعی روش مخزن apt را بردارید.

روش پایدار: مخزن رسمی apt

کلید GPG و مخزن داکر را دستی اضافه و بعد پنج بسته‌ی رسمی را نصب می‌کنید:

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# ${UBUNTU_CODENAME:-$VERSION_CODENAME}: روی مشتقات اوبونتو کد اسم درست را برمی‌گرداند
cat > /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

این فرمت deb822 است که مستندات رسمی امروز می‌نویسند؛ فایل تک‌خطی docker.list هنوز کار می‌کند اما در حال کنارگذاشته‌شدن است — اگر چنین فایلی دارید، apt modernize-sources تبدیلش می‌کند. بسته‌ی docker-compose-plugin هم دستور docker compose (با فاصله، نه خط تیره) را در اختیارتان می‌گذارد.

شماره‌ها با نسخه‌ی روزِ مخزن فرق می‌کند؛ مهم این است که هر دو دستور بدون خطا جواب دهند:

docker --version
docker compose version

Docker version 29.8.0, build ...
Docker Compose version v5.5.1

اجرا بدون sudo

برای آنکه هر بار sudo نزنید، کاربر مدیر خودتان را به گروه docker اضافه و یک‌بار خارج/وارد شوید. در شل root نام کاربر را صریح بنویسید؛ $USER آنجا یعنی root:

usermod -aG docker mehran     # به‌جای mehran نام کاربری خودتان

# یا از داخل شل همان کاربر:
sudo usermod -aG docker $USER
⚠️ عضویت در گروه docker عملاً دسترسی root می‌دهد و مستندات رسمی هم همین را می‌گویند: هر عضو می‌تواند کانتینری بسازد که کل فایل‌سیستم سرور را mount کند. فقط کاربر مدیر خودتان را اضافه کنید، نه کاربر یک برنامه‌ی وب.

خطای 403 داکر هاب روی سرور ایران را چطور رد کنیم؟

داکر برای رعایت مقررات کنترل صادرات آمریکا آی‌پی‌های ایران را روی رجیستری خودش مسدود کرده است؛ اولین docker pull این‌طور تمام می‌شود:

docker pull nginx:alpine

Error response from daemon: error parsing HTTP 403 response body:
invalid character '<' looking for beginning of value:
"<html><body><h1>403 Forbidden</h1>
Since Docker is a US company, we must comply with US export
control regulations..."

این یکی را برخلاف گام نصب هیچ تنظیم DNS‌ای حل نمی‌کند؛ رجیستری بر اساس آی‌پی مبدأ تصمیم می‌گیرد. سه راه عملی دارید:

۱. تنظیم آینه‌ی رجیستری

داکر امکان معرفی «آینه» دارد: ایمیج‌ها را به‌جای داکر هاب از یک سرور واسط می‌گیرد. آدرس آینه در /etc/docker/daemon.json می‌نشیند:

{
  "registry-mirrors": ["https://mirror-address.example"],
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
systemctl restart docker
docker info | grep -A1 "Registry Mirrors"

 Registry Mirrors:
  https://mirror-address.example/

دو محدودیت را بدانید: registry-mirrors فقط برای ایمیج‌های داکر هاب کار می‌کند و روی ghcr.io اثری ندارد؛ و آینه‌های عمومی می‌آیند و می‌روند، پس برای زیرساخت جدی رویشان حساب نکنید.

۲. کشیدن ایمیج روی سروری در خارج و انتقال آن

اگر سروری خارج از کشور دارید، ایمیج را آنجا بکشید و بسته‌بندی‌شده منتقلش کنید؛ این روش به هیچ آینه‌ای وابسته نیست:

# معماری مقصد را صریح بدهید؛ سرور مبدأ و سرور مقصد یکی نیستند
docker pull --platform linux/amd64 nginx:alpine
docker save nginx:alpine | gzip > nginx-alpine.tgz
scp nginx-alpine.tgz root@IRAN_SERVER_IP:/root/

ssh root@IRAN_SERVER_IP 'gunzip -c /root/nginx-alpine.tgz | docker load'
Loaded image: nginx:alpine
💡 از داکر ۲۹ و فقط روی نصب‌های تازه، انبار پیش‌فرض ایمیج containerd است (سرورِ ارتقایافته روی انبار قدیمی می‌ماند). خروجی docker save آن‌وقت آرشیو OCI است و داکرِ خیلی قدیمی‌تر بارش نمی‌کند — دو سر را روی نسخه‌های نزدیک نگه دارید.

همان سرور خارجی می‌تواند رجیستری خصوصی هم باشد تا سرور داخلی مستقیم از آن pull کند؛ اگر ندارید راهنمای تهیه‌ی سرور خارج را ببینید؛ در مهران هاست سرورهای خارج در دیتاسنترهای هتزنر (Hetzner) با سفارش و پرداخت ریالی از همان کیف پول ارائه می‌شوند — کم‌تأخیرترین لوکیشن تا سرور داخلی‌تان را بردارید.

۳. ساختن ایمیج روی همان سرور

اگر Dockerfile دارید و ایمیج پایه در دسترس است، build را روی همان سرور بزنید — با این حواس‌جمعی که build هم معمولاً از npm یا pip بسته می‌کشد.

💡 سقف دانلود از داکر هاب: جدا از تحریم، داکر هاب سقف دریافت هم دارد — طبق مستندات رسمی ۱۰۰ دریافت در هر ۶ ساعت برای کاربر ناشناس (هر آی‌پی نسخه‌ی ۴ یا هر ساب‌نت /64 از IPv6) و ۲۰۰ برای حساب رایگانِ واردشده. روی سرور CI با docker login وارد شوید تا سقف دو برابر شود.

اولین کانتینر واقعی: وب‌سرور

docker run -d --name web -p 8080:80 nginx:alpine

ترجمه‌ی دستور: ایمیج سبک nginx:alpine را بگیر، در پس‌زمینه اجرا کن (-d) و پورت ۸۰ کانتینر را روی ۸۰۸۰ سرور باز کن؛ حالا http://IP:8080 بالاست. چند دستور روزمره:

docker ps               # چه چیزهایی در حال اجراست؟
docker logs -f web      # لاگ زنده
docker exec -it web sh  # ورود به داخل کانتینر
docker stop web && docker rm web   # توقف و حذف

در عیب‌یابی، docker ps -a کانتینرهای خاموش را هم نشان می‌دهد — کانتینری که «بالا نمی‌آید» معمولاً بالا آمده، خطا داده و مرده. ستون STATUS کد خروج را می‌گوید و docker logs دلیلش را:

# خروجی پیش‌فرض هفت ستون دارد؛ اینجا کوتاه شده است
docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"

CONTAINER ID   IMAGE          STATUS                     NAMES
9f2c1a4b7e83   myapp:1.0      Exited (1) 12 seconds ago  app
c41d8a9f0b62   nginx:alpine   Up 4 minutes               web

و اگر پورت میزبان اشغال باشد، داکر با bind: address already in use شکست می‌خورد؛ با ss -tlnp | grep 8080 ببینید چه چیزی رویش نشسته.

ایمیج، کانتینر، ولوم و شبکه چه فرقی دارند؟

بیشتر سردرگمی‌های روزهای اول از قاطی‌شدن همین مفاهیم می‌آید؛ ستون آخر جدول از همه مهم‌تر است:

مفهومچیست؟دستور بررسیبا حذف کانتینر چه می‌شود؟
ایمیج (Image)قالب فقط‌خواندنی فایل‌سیستم و پیکربندی اپdocker imagesباقی می‌ماند تا دستی حذفش کنید
کانتینر (Container)اجرای زنده‌ی ایمیج، با لایه‌ی نوشتنی موقتdocker ps -aخودش و لایه‌ی نوشتنی‌اش پاک می‌شوند
ولوم (Volume)فضای ذخیره‌سازی مدیریت‌شده‌ی داکر، بیرون کانتینرdocker volume lsباقی می‌ماند؛ داده‌ی دیتابیس اینجا امن است
bind-mountپوشه‌ای از سرور که داخل کانتینر سوار می‌شودdocker inspectباقی می‌ماند؛ روی فایل‌سیستم خود سرور
شبکه (Network)شبکه‌ی داخلی که کانتینرها با نام سرویس هم را می‌یابندdocker network lsباقی می‌ماند؛ Compose آن را جدا می‌سازد

پیام پنهان جدول را تازه‌کارها گران یاد می‌گیرند: هرچه در لایه‌ی نوشتنی کانتینر بنویسید — آپلود، SQLite، لاگ اپ — با اولین docker rm می‌رود؛ داده‌ی مهم فقط در volume.

Docker Compose: اپ چندسرویسه با یک فایل

اپ واقعی چند قطعه دارد — وردپرس + دیتابیس — و Compose هر دو را با هم تعریف می‌کند. فرض کنید compose.yaml در پوشه‌ای به نام wpsite است؛ نام پوشه بعداً پیشوند نام ولوم‌ها می‌شود:

services:
  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: strong-root-pass
      MARIADB_DATABASE: wp
      MARIADB_USER: wpuser
      MARIADB_PASSWORD: strong-user-pass
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10

  wordpress:
    image: wordpress:7-php8.4-apache
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wp
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: strong-user-pass
    volumes:
      - wp_data:/var/www/html
    depends_on:
      db:
        condition: service_healthy

volumes:
  db_data:
  wp_data:

و بعد:

docker compose up -d

یک وردپرس کامل با دیتابیس بالا آمد. docker compose down همه را جمع می‌کند (داده‌ها در volume می‌مانند) و docker compose logs -f wordpress لاگ یک سرویس را. restart: unless-stopped هم سرویس را بعد از ری‌بوت برمی‌گرداند؛ برخلاف always، آنچه عمداً خوابانده‌اید خوابیده می‌ماند. چهار تفاوت این فایل با نمونه‌های اینترنت عمدی است:

  • condition: service_healthy به‌جای depends_on ساده. شکل کوتاه depends_on فقط تضمین می‌کند کانتینر دیتابیس شروع شده، نه آماده‌ی اتصال؛ دیتابیس در اولین اجرا چند ده ثانیه مشغول ساخت اولیه است و وردپرس در این فاصله خطا می‌دهد.
  • کاربر جداگانه‌ی دیتابیس به‌جای root. اگر اپ روزی در معرض نفوذ قرار بگیرد، خسارت به همان یک دیتابیس محدود می‌ماند.
  • تگ مهارشده به‌جای latest. wordpress:7-php8.4-apache نسخه‌ی PHP، وب‌سرور و شاخه‌ی اصلی وردپرس را قفل می‌کند — ولی خودش هم شناور است و با هر انتشار ۷.x جلو می‌رود؛ latest از این هم بازتر است. اگر باید دقیقاً همان چیزی اجرا شود که تست کرده‌اید، تگ کامل مثل wordpress:7.1.0-php8.4-apache بردارید یا ایمیج را با اثر انگشتش (image@sha256:...) پین کنید.
  • 127.0.0.1 جلوی پورت. دلیلش موضوع بخش بعد است.

چرا فایروال جلوی پورت‌های داکر را نمی‌گیرد؟

این خطرناک‌ترین تله‌ی داکر روی سرور عمومی است. وقتی پورتی را با -p منتشر می‌کنید، داکر قواعدش را مستقیم در جدول nat آی‌پی‌تیبلز می‌نویسد؛ بسته در زنجیره‌ی PREROUTING به آی‌پی داخلی کانتینر ترجمه می‌شود و از آن به بعد دیگر «برای خودِ سرور» نیست، پس وارد زنجیره‌ی INPUT — جایی که قواعد معمول UFW می‌نشینند — نمی‌شود و از FORWARD رد می‌شود. UFW در FORWARD هم قاعده دارد، اما داکر قواعد خودش را بالاتر از آن‌ها تزریق می‌کند و بسته پیش از رسیدن به قواعد UFW پذیرفته شده است. مستندات رسمی: ترافیک کانتینر «پیش از رسیدن به تنظیمات فایروال ufw منحرف می‌شود».

نتیجه‌ی عملی: ufw deny 3306 زده‌اید و ufw status نشانش می‌دهد، اما اگر پورت دیتابیس را با -p 3306:3306 منتشر کرده باشید، از سراسر اینترنت در دسترس است.

⚠️ مطمئن‌ترین راه‌حل، اصلاً منتشرنکردن پورت است. سرویس‌های یک فایل Compose با نام سرویس همدیگر را می‌بینند؛ وردپرس با db:3306 وصل می‌شود بی‌آنکه پورتی روی میزبان باز شود. برای دیتابیس، کش و صف هیچ‌وقت ports ننویسید.

اگر پورتی باید باز باشد ولی فقط از خود سرور — مثلاً برای پروکسی Nginx میزبان — به لوکال‌هاست ببندیدش:

docker run -d -p 127.0.0.1:8080:80 nginx:alpine   # فقط از خود سرور
docker run -d -p 8080:80 nginx:alpine             # از کل اینترنت

و اگر می‌خواهید فیلترینگ فایروال روی ترافیک کانتینرها هم اعمال شود، قواعد را در زنجیره‌ی DOCKER-USER بنویسید، نه در UFW — این برای بک‌اند پیش‌فرض یعنی iptables است؛ از داکر ۲۹ به بعد بک‌اند آزمایشی nftables هم هست (کلید firewall-backend) که در آن DOCKER-USER وجود ندارد. لایه‌های دیگر سخت‌سازی سرور را در چک‌لیست امنیت سرور لینوکس جمع کرده‌ایم.

داده‌ها کجا می‌مانند و چطور از آن‌ها بکاپ بگیریم؟

هرچه باید از حذف کانتینر جان سالم به در ببرد، در ولوم یا bind-mount می‌نشیند:

docker volume ls

DRIVER    VOLUME NAME
local     wpsite_db_data
local     wpsite_wp_data

تغییر نام پوشه‌ی پروژه یعنی Compose ولوم‌های تازه می‌سازد و داده‌ها «گم» به نظر می‌رسند، در حالی که ولوم قدیمی سر جایش است.

برای بکاپ دیتابیس فایل‌های خام ولوم را کپی نکنید؛ کپیِ حین اجرا نامعتبر است. راه درست، دامپ از داخل کانتینر با ابزار خود دیتابیس است:

mkdir -p /backup
cd /srv/wpsite            # همان پوشه‌ای که compose.yaml در آن است
set -o pipefail           # اسکریپت را با bash اجرا کنید
OUT=/backup/wp-$(date +%F).sql.gz

docker compose exec -T db sh -c \
  'export MYSQL_PWD="$MARIADB_ROOT_PASSWORD"; exec mariadb-dump -u root --single-transaction wp' \
  | gzip > "$OUT" || { rm -f "$OUT"; echo "dump failed"; exit 1; }

ls -lh "$OUT"

دو جزئیات حیاتی: اول، پسورد از داخل خود کانتینر خوانده می‌شود — متغیر MARIADB_ROOT_PASSWORD از قبل آنجاست — پس روی میزبان در هیچ آرگومانی نمی‌نشیند؛ اگر با -e MYSQL_PWD بدهیدش، همان فرمان پسورد را در ps میزبان لو می‌دهد. دوم، gzip -t فقط سلامت فایل gz را می‌سنجد و دامپِ شکست‌خورده هم فایل خالیِ سالمی می‌دهد؛ کد خروجِ خودِ دامپ را باید دید و در یک لوله فقط set -o pipefail آن را می‌دهد.

و برای فایل‌های ایستا کانتینری موقت را به ولوم وصل و آرشیو بگیرید — به‌جای wpsite_ پیشوند پروژه‌ی خودتان را بگذارید، چون نام اشتباه خطا نمی‌دهد: داکر ولوم خالیِ تازه می‌سازد و آرشیو خالی درمی‌آید.

docker run --rm -v wpsite_wp_data:/data -v /backup:/backup \
  alpine tar czf /backup/wp-files-$(date +%F).tgz -C /data .

هر دو را در یک کران روزانه بگذارید، با یک حواس‌جمعی: docker compose فایل compose.yaml را نسبت به پوشه‌ی جاری پیدا می‌کند و کران در پوشه‌ی خانگی اجرا می‌شود؛ بدون cd یا -f /srv/wpsite/compose.yaml کار با no configuration file provided می‌ایستد. و بکاپی که بازیابی‌اش را امتحان نکرده‌اید بکاپ نیست — روی یک سرور ساعتی تازه تستش کنید.

دیسک سرور پر شد؛ لاگ و ایمیج‌های داکر را مهار کنید

شایع‌ترین شکایت از سرورهای داکری بعد از چند هفته: دیسک بی‌دلیل پر شده. دو مقصر دارد.

مقصر اول، لاگ کانتینرها. درایور پیش‌فرض json-file است و max-size در آن نامحدود است؛ هیچ چرخشی در کار نیست و کانتینری پرحرف ماه‌ها لاگ روی هم می‌ریزد. راه‌حل همان /etc/docker/daemon.json است:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

با این تنظیم هر کانتینر حداکثر سه فایل ده‌مگابایتی نگه می‌دارد — اما فقط کانتینرهای تازه‌ساخته؛ موجودها باید دوباره ساخته شوند (docker compose up -d --force-recreate).

مقصر دوم، ایمیج‌ها و کش build. هر بار ایمیج جدیدی می‌کشید، نسخه‌ی قبلی بدون نام روی دیسک می‌ماند؛ اول وضع را ببینید:

docker system df

TYPE            TOTAL   ACTIVE  SIZE     RECLAIMABLE
Images          14      3       6.2GB    4.8GB (77%)
Containers      5       3       128MB    41MB (32%)
Local Volumes   4       3       1.1GB    210MB (18%)
Build Cache     62      0       3.4GB    3.4GB (100%)

و بعد پاک‌سازی:

docker image prune -a     # ایمیج‌هایی که هیچ کانتینری از آن‌ها استفاده نمی‌کند
docker builder prune      # کش build
docker container prune    # کانتینرهای متوقف‌شده
⚠️ سوئیچ --volumes در docker system prune فقط ولوم‌های بی‌نام را می‌برد؛ مستندات رسمی می‌گویند ولوم‌های نام‌دار دست‌نخورده می‌مانند و -a هم این را عوض نمی‌کند. خطر واقعی docker volume prune --all است که ولوم نام‌دار Compose را هم می‌برد — سرویسی که موقتاً پایین باشد «بدون استفاده» حساب می‌شود.

محدودکردن مصرف رم هم ارزش دارد تا کانتینری لبریز با OOM کل سرور را نخواباند: --memory=512m روی docker run یا mem_limit: 512m در Compose، و docker stats برای مصرف لحظه‌ای.

Nginx و SSL را جلوی کانتینرها چطور بگذاریم؟

پورت وردپرس را به 127.0.0.1:8080 بستیم؛ پس کاربر واقعی چطور به سایت می‌رسد؟ با یک Nginx روی خود سرور، نه در کانتینر، که درخواست‌ها را پروکسی می‌کند:

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

فعلاً فقط پورت ۸۰ در فایل است؛ listen 443 ssl و مسیر گواهی را Certbot خودش اضافه می‌کند.

چهار هدر بالا اختیاری نیستند. بدون آن‌ها اپ داخل کانتینر آی‌پی همه‌ی بازدیدکنندگان را دروازه‌ی شبکه‌ی داکر می‌بیند — روی bridge پیش‌فرض 172.17.0.1 و روی شبکه‌ی Compose معمولاً 172.18.0.1. X-Real-IP و X-Forwarded-For آی‌پی واقعی را دست‌کم در دسترس اپ می‌گذارند؛ هسته‌ی وردپرس همچنان REMOTE_ADDR را می‌خواند و ثبتش افزونه می‌خواهد. اما X-Forwarded-Proto را خود ایمیج رسمی وردپرس می‌خواند و جلوی حلقه‌ی تغییر مسیرِ «TLS فقط روی Nginx» را می‌گیرد.

دو خطای رایج این معماری: 502 Bad Gateway یعنی کانتینر پشت Nginx پایین است یا روی پورت دیگری گوش می‌دهد؛ و 413 Request Entity Too Large یعنی سقف پیش‌فرض یک‌مگابایتیِ Nginx جلوی آپلود را گرفته — client_max_body_size در بلاک بالا را با upload_max_filesize و post_max_size کانتینر وردپرس هماهنگ کنید.

نصب Nginx و گواهی رایگان با Certbot را در راهنمای Nginx روی اوبونتو نوشته‌ایم. مزیت این معماری: تمدید خودکار گواهی بیرون کانتینرها می‌ماند و به‌روزرسانی اپ ربطی به SSL ندارد.

گردش کار پیشنهادی با سرور ساعتی

یک سناریوی واقعی از داکر و پرداخت ساعتی:

  1. صبح: سرور ۴ گیگی می‌سازید (ساخت خودکار است)، داکر را نصب و compose.yaml را از گیت می‌کشید؛
  2. docker compose up -d — محیط کامل پروژه بالا می‌آید و تمام روز توسعه و تست؛
  3. عصر: push به رجیستری/گیت، خروجی از volumeها، حذف سرور؛
  4. هزینه‌ی کل روز: چند ساعت سرور، نه یک ماه.

چیزی که این گردش کار را شدنی می‌کند، ثبت‌شدن کل محیط در دو فایل متنی است: compose.yaml و اسکریپت راه‌اندازی؛ تا وقتی در گیت باشند، سرور منبعی مصرفی است.

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

سؤالات پرتکرار

برای نصب داکر روی سرور مجازی چه مشخصاتی لازم است؟

سرور باید مبتنی بر KVM باشد تا هسته‌ی مستقل داشته باشد؛ روی مجازی‌سازی‌های اشتراکی‌هسته مثل OpenVZ داکر معمولاً اجرا نمی‌شود. خود موتور داکر سبک است، اما مجموع ایمیج‌ها، کش build و ولوم‌ها فضا می‌خواهد: کمتر از ۲۰ گیگابایت دیسک برای محیطی چندسرویسه واقع‌بینانه نیست و ۲ گیگابایت رم برای یک اپ به‌همراه دیتابیس حداقلِ راحت است.

چرا docker pull روی سرور ایران خطای 403 می‌دهد؟

داکر برای رعایت مقررات کنترل صادرات آمریکا دسترسی آی‌پی‌های چند کشور از جمله ایران را به رجیستری خودش مسدود کرده و پاسخ 403 برمی‌گردد. این 403 از رجیستری می‌آید، نه از نصب یا شبکه‌ی سرور، و با تغییر DNS حل نمی‌شود — برخلاف 403ِ گام نصب که از مخزن download.docker.com می‌آید و با DNS داخلی رد می‌شود. سه راه عملی دارید: معرفی آینه‌ی رجیستری در daemon.json، کشیدن ایمیج روی سروری خارج از کشور و انتقال آن با docker save و docker load، یا ساختن ایمیج روی همان سرور.

چرا با وجود فعال‌بودن فایروال، پورت کانتینر از اینترنت باز است؟

چون داکر هنگام انتشار پورت با سوئیچ -p قواعد خودش را در جدول nat آی‌پی‌تیبلز می‌نویسد و بسته‌ها در زنجیره‌ی PREROUTING به کانتینر منحرف می‌شوند، بدون آنکه وارد زنجیره‌ی INPUT شوند — همان زنجیره‌ای که قواعد UFW در آن قرار دارد. UFW در FORWARD هم قاعده دارد، اما داکر قواعد خودش را بالاتر از آن‌ها تزریق می‌کند. راه‌حل، منتشرنکردن پورت سرویس‌های داخلی است؛ سرویس‌های یک فایل Compose با نام یکدیگر را می‌بینند. اگر پورت لازم است، با 127.0.0.1:8080:80 فقط روی لوکال‌هاست ببندیدش.

تفاوت docker compose و docker-compose چیست؟

نسخه‌ی جدید و پشتیبانی‌شده، همان docker compose با فاصله است که به‌صورت افزونه‌ی رسمی و با بسته‌ی docker-compose-plugin نصب می‌شود. نسخه‌ی قدیمی docker-compose با خط تیره ابزاری جداگانه به زبان پایتون بود که دیگر توسعه داده نمی‌شود. کلید version در ابتدای فایل پیکربندی هم منسوخ شده و نسخه‌های جدید با هشدار نادیده‌اش می‌گیرند؛ بهتر است حذفش کنید.

آیا با حذف کانتینر، اطلاعات دیتابیس از بین می‌رود؟

اگر دیتابیس روی یک ولوم یا bind-mount نشسته باشد، خیر: حذف کانتینر فقط لایه‌ی نوشتنی موقت آن را پاک می‌کند و ولوم دست‌نخورده می‌ماند تا کانتینر بعدی به آن وصل شود. اما اگر ولومی تعریف نکرده باشید، داده داخل خود کانتینر است و با docker rm برای همیشه می‌رود. دو خطر دیگر: دستور docker volume prune --all که ولوم نام‌دارِ بدون کانتینر را هم می‌برد، و تغییر نام پوشه‌ی پروژه که ولوم‌های تازه می‌سازد.

آموزش‌های مرتبط

آماده‌ی تمرین عملی هستید؟

سرور ابری ساعتی مهران هاست در ۶۰ ثانیه تحویل می‌شود — تمرین کنید و فقط بابت همان ساعت‌ها پرداخت کنید. هزینه را پیش از ثبت‌نام با محاسبه‌گر برآورد کنید.