بک اند

دیپلوی پروژه با Docker

جمله‌ی «روی سیستم من کار می‌کرد» با Docker تقریباً از بین می‌رود، چون برنامه با تمام وابستگی‌هایش در یک تصویر ثابت بسته‌بندی می‌شود. در این راهنما مسیر دیپلوی با Docker را از Dockerfile تا اجرای امن روی سرور مرور می‌کنیم.

  • نویسنده تیم تتا
  • ۸ دقیقه مطالعه
  • به‌روزرسانی:
دیپلوی پروژه با Docker

چرا Docker برای دیپلوی؟

بدون کانتینر، دیپلوی یعنی نصب دستی نسخه‌ی درست زبان، کتابخانه‌ها و تنظیمات روی هر سرور؛ و اختلاف کوچک میان سرورها باگ‌هایی می‌سازد که پیدا کردنشان روزها طول می‌کشد. در Docker همان تصویری که در تست اجرا شد، بدون تغییر به تولید می‌رود.

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

نوشتن Dockerfile چندمرحله‌ای

در روش چندمرحله‌ای، مرحله‌ی اول ابزارهای ساخت (کامپایلر، devDependencies) را دارد و مرحله‌ی آخر فقط خروجی نهایی را. نمونه برای یک برنامه‌ی Node.js:

dockerfile
# ---- build stage ----
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ---- runtime stage ----
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
  • ابتدا فقط package*.json را کپی و نصب می‌کنیم تا لایه‌ی وابستگی‌ها تا وقتی آن فایل‌ها عوض نشده‌اند از کش استفاده شود و ساخت سریع بماند.
  • USER node برنامه را با کاربر غیر root اجرا می‌کند؛ اگر نفوذی رخ دهد، خسارت محدودتر است.
  • فایل .dockerignore بسازید و node_modules، .git و فایل‌های .env را در آن بنویسید تا وارد تصویر نشوند.

جزئیات همه‌ی دستورها در مرجع Dockerfile آمده است.

تعریف کل پشته با docker compose

برنامه‌ی واقعی معمولاً چند بخش دارد: وب‌اپ، پایگاه‌داده و کش. فایل compose همه را در یک مکان و با یک دستور بالا می‌آورد:

yaml
services:
  app:
    image: registry.example.com/shop-api:1.4.2
    restart: unless-stopped
    env_file: .env
    ports: ["127.0.0.1:3000:3000"]
    depends_on:
      db: { condition: service_healthy }
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 15s
      retries: 3
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_pass
    volumes: [dbdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
  cache:
    image: redis:7-alpine
    restart: unless-stopped
volumes:
  dbdata:

چند نکته: پورت برنامه فقط روی 127.0.0.1 منتشر شده تا از بیرون مستقیم در دسترس نباشد و ترافیک از Nginx عبور کند؛ پایگاه‌داده و Redis اصلاً پورت منتشر نمی‌کنند و فقط در شبکه‌ی داخلی compose دیده می‌شوند. برچسب نسخه‌ی مشخص (1.4.2) به‌جای latest برگشت را قابل‌پیش‌بینی می‌کند. الگوهای استفاده از Redis را در کش کردن هوشمند با Redis ببینید.

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

تنظیمات، رمزها و داده‌ی ماندگار

اصل دوازده‌عاملی: تنظیمات از محیط می‌آید، نه از کد. فایل .env را فقط روی سرور نگه دارید و هیچ‌وقت در Git یا تصویر قرار ندهید. برای رمزهای حساس‌تر از Docker secrets یا مدیر رمز ابری استفاده کنید و در برنامه از مسیر فایل بخوانید.

کانتینر موقتی است؛ هر بار که دوباره ساخته شود، فایل‌های داخلش از بین می‌رود. هر چیزی که باید بماند (فایل پایگاه‌داده، آپلودها) را روی volume بگذارید و از آن پشتیبان بگیرید. یک پشتیبان که هرگز بازیابی‌اش را آزمایش نکرده‌اید، پشتیبان حساب نمی‌شود.

bash
# nightly logical backup of the Postgres volume's data
docker compose exec -T db pg_dump -U postgres shop | gzip > /backups/shop-$(date +%F).sql.gz

دیپلوی روی سرور و به‌روزرسانی

جریان ساده و قابل‌اعتماد برای یک سرور:

  1. در CI تصویر را با برچسب نسخه می‌سازید و به رجیستری (Docker Hub، GitHub Container Registry یا رجیستری خصوصی) می‌فرستید.
  2. روی سرور برچسب جدید را در docker-compose.yml می‌گذارید (یا متغیر نسخه را عوض می‌کنید).
  3. docker compose pull سپس docker compose up -d فقط سرویس‌های تغییرکرده را دوباره می‌سازد.
  4. health check را بررسی کنید و اگر خراب بود، برچسب قبلی را برگردانید و دوباره up -d بزنید.
bash
docker compose pull app
docker compose up -d app
docker compose ps          # STATUS should say "healthy"
docker compose logs -f --tail=50 app
docker image prune -f      # reclaim disk from old layers

با یک سرویس، در لحظه‌ی جایگزینی چند ثانیه قطعی ممکن است. اگر قطعی صفر لازم دارید، دو نمونه از برنامه را پشت Nginx اجرا و یکی‌یکی به‌روزرسانی کنید، یا از یک ابزار ارکستراسیون استفاده کنید. دیپلوی خودکار با CI را هم می‌توانید با تست‌های اجراشده در تست خودکار در لاراول ترکیب کنید تا فقط نسخه‌ی سالم منتشر شود.

چک‌لیست تولید و نظارت

  • تصویر پایه‌ی کوچک (alpine یا slim) با برچسب نسخه، نه latest.
  • اجرای برنامه با کاربر غیر root و فایل‌سیستم فقط‌خواندنی در صورت امکان.
  • restart: unless-stopped و health check برای هر سرویس.
  • محدودیت حافظه و CPU برای کانتینرها تا یک سرویس کل سرور را نبلعد.
  • چرخش لاگ فعال (max-size و max-file در درایور json-file) تا دیسک پر نشود.
  • پشتیبان خودکار و تست بازیابی دوره‌ای.

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

پرسش‌های متداول

Docker و docker compose چه فرقی دارند؟

Docker یک کانتینر را می‌سازد و اجرا می‌کند. docker compose چند کانتینر مرتبط، شبکه و volumeهایشان را در یک فایل YAML تعریف می‌کند و با یک دستور بالا می‌آورد؛ برای برنامه‌های چندبخشی ایده‌آل است.

آیا برای یک سایت کوچک هم Docker لازم است؟

الزامی نیست، ولی دیپلوی را قابل‌تکرار و برگشت‌پذیر می‌کند و راه‌اندازی مجدد روی سرور جدید را ساده می‌کند. برای پروژه‌ای که قرار است رشد کند، هزینه‌ی یادگیری آن معمولاً می‌ارزد.

داده‌های پایگاه‌داده در Docker از بین می‌روند؟

فقط اگر روی volume نباشند. با تعریف volume نام‌گذاری‌شده، داده با حذف و ساخت دوباره‌ی کانتینر باقی می‌ماند. با این حال حتماً پشتیبان جدا هم بگیرید.

چطور حجم تصویر Docker را کم کنم؟

از تصویر پایه‌ی alpine یا slim، ساخت چندمرحله‌ای، نصب فقط وابستگی‌های تولید و فایل .dockerignore استفاده کنید. معمولاً حجم از چند صد مگابایت به چند ده مگابایت می‌رسد.

دیدگاه‌ها

    هنوز دیدگاهی ثبت نشده است. اولین نفر باشید.

    دیدگاه‌ها پس از بررسی منتشر می‌شوند.