Docker چیست؟ آموزش کامل Docker و Docker Compose با پروژه عملی هوش مصنوعی

در این آموزش Docker را از صفر یاد می‌گیرید و با مفاهیم Image، Container، Volume، Network و Docker Compose آشنا می‌شوید. در پایان یک API هوش مصنوعی واقعی با FastAPI، درواره و Redis را داخل کانتینر اجرا می‌کنیم.

Share
Docker چیست؟ آموزش کامل Docker و Docker Compose با پروژه عملی هوش مصنوعی

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

Docker برای حل همین مسئله ساخته شده است. با Docker می‌توانید برنامه و وابستگی‌های آن را داخل یک محیط استاندارد به نام Container قرار دهید و همان محیط را روی لپ‌تاپ، سرور، سرویس CI/CD یا زیرساخت ابری اجرا کنید.

در این آموزش، Docker و Docker Compose را از صفر یاد می‌گیریم و در پایان یک پروژه کاربردی می‌سازیم: API هوش مصنوعی با Python و FastAPI که به API درواره متصل است و برای کاهش درخواست‌های تکراری از Redis Cache استفاده می‌کند.

Docker چیست؟

Docker پلتفرمی برای ساخت، توزیع و اجرای برنامه‌ها داخل Container است. Container محیطی جداشده و قابل‌انتقال برای اجرای یک برنامه محسوب می‌شود و کد، Runtime، کتابخانه‌ها و تنظیمات موردنیاز آن را در کنار هم قرار می‌دهد.

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

به زبان ساده، Docker کمک می‌کند این جمله کمتر شنیده شود:

روی سیستم من که درست کار می‌کرد!

وقتی برنامه داخل Docker Image ساخته شود، تمام اعضای تیم می‌توانند همان Image را اجرا کنند و محیطی نزدیک به یکدیگر داشته باشند.

Container چیست؟

Container یک فرایند جداشده است که برنامه را همراه با وابستگی‌های موردنیاز اجرا می‌کند.

یک Container معمولاً شامل این موارد است:

  • کد برنامه
  • نسخه مشخصی از زبان برنامه‌نویسی
  • کتابخانه‌های موردنیاز
  • ابزارهای سیستمی ضروری
  • تنظیمات اجرای برنامه
  • فرمان شروع برنامه

Container معمولاً از Kernel سیستم‌عامل میزبان استفاده می‌کند و به همین دلیل نسبت به ماشین مجازی سبک‌تر است. بااین‌حال، Container و ماشین مجازی دقیقاً یک کاربرد ندارند و انتخاب میان آن‌ها به معماری و سطح جداسازی موردنیاز بستگی دارد.

تفاوت Container و ماشین مجازی

ماشین مجازی یا Virtual Machine معمولاً یک سیستم‌عامل کامل را همراه با Kernel خودش اجرا می‌کند. Container بیشتر اجزای سطح سیستم‌عامل میزبان را به اشتراک می‌گذارد و محیط اجرای برنامه را جدا می‌کند.

ویژگی‌های معمول Container:

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

ویژگی‌های معمول ماشین مجازی:

  • سیستم‌عامل مستقل
  • جداسازی عمیق‌تر
  • مصرف منابع بیشتر
  • زمان راه‌اندازی طولانی‌تر
  • مناسب برای اجرای سیستم‌عامل یا بار کاری کاملاً مستقل

Docker همیشه جایگزین ماشین مجازی نیست؛ اما برای بسته‌بندی و اجرای بسیاری از برنامه‌های وب، APIها، Workerها و سرویس‌های هوش مصنوعی انتخاب مناسبی است.

مفاهیم اصلی Docker

برای یادگیری Docker باید چند اصطلاح کلیدی را بشناسید.

Docker Image چیست؟

Docker Image یک بسته فقط‌خواندنی و نسخه‌بندی‌شده است که دستورالعمل‌ها و فایل‌های لازم برای اجرای برنامه را در خود دارد.

Image را می‌توان مانند یک قالب آماده در نظر گرفت. برای مثال، Image پروژه ما شامل Python، وابستگی‌های FastAPI و کد برنامه خواهد بود.

Docker Container چیست؟

Container نمونه در حال اجرای یک Image است.

از یک Image می‌توانید چند Container مستقل ایجاد کنید:

Docker Image
├── Container 1
├── Container 2
└── Container 3

اگر Image را مانند کلاس در برنامه‌نویسی در نظر بگیریم، Container تقریباً شبیه نمونه‌ای ساخته‌شده از آن کلاس است.

Dockerfile چیست؟

Dockerfile یک فایل متنی شامل دستورالعمل‌های ساخت Image است.

در Dockerfile مشخص می‌کنیم:

  • Image پایه چه باشد
  • مسیر کاری کجا باشد
  • چه فایل‌هایی کپی شوند
  • چه وابستگی‌هایی نصب شوند
  • برنامه با چه دستوری اجرا شود

Docker Registry چیست؟

Registry محلی برای ذخیره و توزیع Docker Imageها است. Docker Hub یکی از Registryهای شناخته‌شده محسوب می‌شود، اما سازمان‌ها می‌توانند Registry خصوصی نیز داشته باشند.

Docker Volume چیست؟

Volume فضای ذخیره‌سازی پایدار برای Container است. اطلاعات ذخیره‌شده در لایه داخلی Container ممکن است با حذف Container از بین بروند؛ اما داده‌های Volume مستقل از چرخه عمر Container باقی می‌مانند.

طبق مستندات رسمی Docker Volume، Volumeها توسط Docker مدیریت می‌شوند و برای نگهداری داده‌ای که باید بعد از حذف Container نیز باقی بماند مناسب‌اند.

Docker Network چیست؟

Network امکان ارتباط کنترل‌شده Containerها با یکدیگر را فراهم می‌کند. در Docker Compose، سرویس‌های یک پروژه به‌صورت پیش‌فرض می‌توانند از طریق نام سرویس یکدیگر را پیدا کنند.

برای مثال، برنامه ما به‌جای آدرس IP متغیر Redis از نام سرویس redis استفاده می‌کند:

redis://redis:6379/0

طبق مستندات Network در Docker Compose، Compose به‌صورت پیش‌فرض یک Network برای برنامه ایجاد می‌کند و سرویس‌ها در آن با نام سرویس قابل شناسایی هستند.

تفاوت Image و Container

این تفاوت را می‌توان با یک مثال ساده توضیح داد:

  • Image مانند فایل نصب یا قالب آماده برنامه است.
  • Container نسخه در حال اجرای آن Image است.
  • Dockerfile دستور ساخت Image را تعریف می‌کند.
  • Registry محل نگهداری Image است.

دستور زیر یک Image می‌سازد:

docker build -t darvareh-ai-api:1.0 .

دستور زیر از آن Image یک Container اجرا می‌کند:

docker run --name darvareh-api darvareh-ai-api:1.0

Docker Compose چیست؟

Docker Compose ابزاری برای تعریف و اجرای برنامه‌های چندکانتینری است. تنظیمات سرویس‌ها، Networkها، Volumeها و متغیرهای موردنیاز در یک فایل YAML نوشته می‌شوند.

در نسخه‌های جدید Docker، فرمان Compose معمولاً به این شکل اجرا می‌شود:

docker compose

فرم قدیمی‌تر آن چنین بود:

docker-compose

در این مقاله از ساختار جدید docker compose استفاده می‌کنیم.

بر اساس مستندات رسمی Docker Compose، Compose می‌تواند تمام اجزای یک برنامه چندکانتینری را در یک فایل تعریف و با یک فرمان اجرا کند.

برای نمونه، پروژه ما دو سرویس خواهد داشت:

  • سرویس app برای اجرای FastAPI
  • سرویس redis برای Cache پاسخ‌ها

سپس هر دو سرویس با این فرمان اجرا می‌شوند:

docker compose up

تفاوت Dockerfile و compose.yaml

Dockerfile توضیح می‌دهد Image برنامه چگونه ساخته شود.

فایل compose.yaml توضیح می‌دهد Containerهای برنامه چگونه اجرا و به یکدیگر متصل شوند.

Dockerfile معمولاً شامل این موارد است:

  • Image پایه
  • نصب وابستگی‌ها
  • کپی کد
  • تنظیم کاربر
  • فرمان اجرای برنامه

فایل Compose معمولاً شامل این موارد است:

  • فهرست سرویس‌ها
  • Image یا مسیر Build
  • Portها
  • Volumeها
  • Networkها
  • متغیرهای محیطی
  • وابستگی سرویس‌ها
  • Healthcheck
  • سیاست Restart

نصب Docker

نصب در ویندوز

در ویندوز معمولاً Docker Desktop نصب می‌شود. نسخه مناسب سیستم خود را از صفحه رسمی دریافت Docker انتخاب کنید.

Docker Desktop در بسیاری از سیستم‌های ویندوز از WSL 2 استفاده می‌کند. پس از نصب ممکن است لازم باشد سیستم را Restart کنید.

نصب در macOS

برای macOS نیز Docker Desktop در نسخه‌های متناسب با پردازنده‌های Apple Silicon و Intel ارائه می‌شود. هنگام دانلود، معماری درست دستگاه خود را انتخاب کنید.

نصب در Linux

در توزیع‌های Linux می‌توانید Docker Engine و افزونه Docker Compose را مطابق راهنمای رسمی همان توزیع نصب کنید. به‌دلیل تغییر نسخه‌ها و مخازن، بهتر است فرمان نصب را مستقیماً از مستندات رسمی Docker دریافت کنید.

بررسی نصب

بعد از نصب، ترمینال را باز کنید:

docker --version

نسخه Compose را بررسی کنید:

docker compose version

سپس یک Container آزمایشی اجرا کنید:

docker run --rm hello-world

اگر پیام آزمایشی Docker نمایش داده شود، نصب و ارتباط Docker CLI با Docker Engine درست است.

معماری پروژه عملی

در این پروژه یک API با FastAPI می‌سازیم که پیام کاربر را دریافت می‌کند و به یکی از مدل‌های هوش مصنوعی درواره می‌فرستد.

Redis نیز پاسخ درخواست‌های تکراری را برای مدت محدودی نگه می‌دارد تا:

  • تعداد درخواست‌های تکراری کاهش یابد
  • زمان پاسخ برای درخواست مشابه کمتر شود
  • هزینه فراخوانی غیرضروری مدل کاهش پیدا کند

معماری پروژه:

Client
  │
  ▼
FastAPI Container
  │
  ├── بررسی Redis Cache
  │
  ├── پاسخ موجود ← Redis Container
  │
  └── پاسخ موجود نیست
          │
          ▼
     Darvareh AI API

ساختار فایل‌ها:

darvareh-docker-ai-api/
├── app.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
├── .dockerignore
├── .gitignore
├── .env.example
└── README.md

مرحله اول: ساخت پوشه پروژه

mkdir darvareh-docker-ai-api
cd darvareh-docker-ai-api

مرحله دوم: دریافت کلید API درواره

برای اجرای پروژه به دو مقدار نیاز دارید:

  • کلید API درواره
  • شناسه مدل موردنظر

در درواره ثبت‌نام و کلید API ایجاد کنید. سپس شناسه مدل مناسب پروژه را از صفحه مدل‌های درواره بردارید.

در این آموزش از مقادیر نمونه زیر استفاده می‌کنیم:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

مرحله سوم: ساخت requirements.txt

فایل requirements.txt را ایجاد کنید:

fastapi>=0.115,<1.0
uvicorn[standard]>=0.34,<1.0
openai>=1.68,<3.0
redis>=5.2,<7.0
python-dotenv>=1.1,<2.0
pydantic>=2.10,<3.0

محدودکردن بازه نسخه وابستگی‌ها احتمال نصب نسخه‌ای با تغییرات ناسازگار را کاهش می‌دهد. برای محیط Production بهتر است پس از تست، نسخه‌های دقیق را در فایل Lock یا requirements نهایی ثبت کنید.

مرحله چهارم: ساخت فایل متغیرهای محیطی

فایل .env.example:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
CACHE_TTL_SECONDS=600

حالا یک نسخه با نام .env بسازید.

در Linux و macOS:

cp .env.example .env

در PowerShell:

Copy-Item .env.example .env

مقادیر واقعی را فقط در فایل .env قرار دهید.

متغیر REDIS_URL را داخل فایل .env قرار نمی‌دهیم؛ زیرا آن را در فایل Compose با نام سرویس داخلی Redis تنظیم خواهیم کرد.

مرحله پنجم: ساخت برنامه FastAPI

فایل app.py:

import hashlib
import json
import os
from contextlib import asynccontextmanager

import redis
from fastapi import FastAPI, HTTPException, Request
from openai import OpenAI
from pydantic import BaseModel, Field


class ChatRequest(BaseModel):
    message: str = Field(min_length=1, max_length=8000)
    temperature: float = Field(default=0.3, ge=0, le=2)
    max_tokens: int = Field(default=800, ge=1, le=2000)


def create_cache_key(
    model: str,
    message: str,
    temperature: float,
    max_tokens: int,
) -> str:
    payload = {
        "model": model,
        "message": message.strip(),
        "temperature": temperature,
        "max_tokens": max_tokens,
    }

    serialized = json.dumps(
        payload,
        sort_keys=True,
        ensure_ascii=False,
    )

    digest = hashlib.sha256(serialized.encode("utf-8")).hexdigest()
    return f"chat:{digest}"


@asynccontextmanager
async def lifespan(app: FastAPI):
    api_key = os.getenv("DARVAREH_API_KEY")
    model_id = os.getenv("DARVAREH_MODEL_ID")
    redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0")
    cache_ttl = int(os.getenv("CACHE_TTL_SECONDS", "600"))

    if not api_key:
        raise RuntimeError("DARVAREH_API_KEY is not configured")

    if not model_id:
        raise RuntimeError("DARVAREH_MODEL_ID is not configured")

    app.state.model_id = model_id
    app.state.cache_ttl = cache_ttl

    app.state.ai_client = OpenAI(
        api_key=api_key,
        base_url="https://api.darvareh.ir/v1",
    )

    app.state.redis_client = redis.Redis.from_url(
        redis_url,
        decode_responses=True,
        socket_connect_timeout=2,
        socket_timeout=2,
    )

    yield

    app.state.ai_client.close()
    app.state.redis_client.close()


app = FastAPI(
    title="Darvareh Docker AI API",
    version="1.0.0",
    lifespan=lifespan,
)


@app.get("/health")
def health(request: Request):
    redis_status = "unavailable"

    try:
        if request.app.state.redis_client.ping():
            redis_status = "ok"
    except redis.RedisError:
        pass

    return {
        "status": "ok",
        "redis": redis_status,
    }


@app.post("/chat")
def chat(payload: ChatRequest, request: Request):
    model_id = request.app.state.model_id

    cache_key = create_cache_key(
        model=model_id,
        message=payload.message,
        temperature=payload.temperature,
        max_tokens=payload.max_tokens,
    )

    try:
        cached_answer = request.app.state.redis_client.get(cache_key)
    except redis.RedisError:
        cached_answer = None

    if cached_answer:
        return {
            "answer": cached_answer,
            "model": model_id,
            "cached": True,
        }

    try:
        response = request.app.state.ai_client.chat.completions.create(
            model=model_id,
            messages=[
                {
                    "role": "system",
                    "content": (
                        "You are a helpful Persian assistant. "
                        "Answer clearly and accurately in Persian."
                    ),
                },
                {
                    "role": "user",
                    "content": payload.message,
                },
            ],
            temperature=payload.temperature,
            max_tokens=payload.max_tokens,
        )

        answer = response.choices[0].message.content

        if not answer:
            raise HTTPException(
                status_code=502,
                detail="مدل پاسخ قابل استفاده‌ای برنگرداند.",
            )

        try:
            request.app.state.redis_client.setex(
                cache_key,
                request.app.state.cache_ttl,
                answer,
            )
        except redis.RedisError:
            pass

        return {
            "answer": answer,
            "model": model_id,
            "cached": False,
        }

    except HTTPException:
        raise

    except Exception:
        raise HTTPException(
            status_code=502,
            detail="دریافت پاسخ از سرویس هوش مصنوعی با مشکل مواجه شد.",
        )

آدرس پایه API درواره:

https://api.darvareh.ir/v1

Endpoint نهایی Chat Completions:

https://api.darvareh.ir/v1/chat/completions

کتابخانه مورد استفاده مسیر chat/completions را به Base URL اضافه می‌کند.

در این نمونه، از Cache به‌صورت Best Effort استفاده شده است؛ یعنی اگر Redis موقتاً در دسترس نباشد، درخواست همچنان می‌تواند مستقیماً به مدل ارسال شود.

مرحله ششم: ساخت Dockerfile

فایل Dockerfile را بدون پسوند ایجاد کنید:

FROM python:3.12-slim AS builder

ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /build

COPY requirements.txt .

RUN pip wheel \
    --no-cache-dir \
    --wheel-dir /wheels \
    -r requirements.txt


FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

RUN groupadd --system appgroup \
    && useradd \
        --system \
        --gid appgroup \
        --create-home \
        appuser

COPY --from=builder /wheels /wheels
COPY requirements.txt .

RUN pip install \
    --no-cache-dir \
    --no-index \
    --find-links=/wheels \
    -r requirements.txt \
    && rm -rf /wheels

COPY --chown=appuser:appgroup app.py .

USER appuser

EXPOSE 8000

HEALTHCHECK \
    --interval=30s \
    --timeout=3s \
    --start-period=10s \
    --retries=3 \
    CMD python -c \
    "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"

CMD [
    "uvicorn",
    "app:app",
    "--host",
    "0.0.0.0",
    "--port",
    "8000",
    "--workers",
    "1"
]

توضیح دستورهای Dockerfile

FROM مشخص می‌کند Image بر اساس چه Image دیگری ساخته شود:

FROM python:3.12-slim

WORKDIR پوشه کاری داخل Image را تعیین می‌کند:

WORKDIR /app

COPY فایل را از Build Context به Image منتقل می‌کند:

COPY app.py .

RUN دستوری را هنگام ساخت Image اجرا می‌کند:

RUN pip install -r requirements.txt

USER کاربر اجرای برنامه را تعیین می‌کند:

USER appuser

اجرای برنامه با کاربر غیر Root برای بسیاری از برنامه‌ها انتخاب مناسب‌تری است.

EXPOSE پورت مورد انتظار برنامه را مستند می‌کند:

EXPOSE 8000

HEALTHCHECK وضعیت سلامت Container را بررسی می‌کند.

CMD فرمان پیش‌فرض اجرای Container را مشخص می‌کند.

چرا Dockerfile چندمرحله‌ای ساخته‌ایم؟

Dockerfile پروژه دارای دو Stage است:

  • builder برای ساخت Wheel وابستگی‌ها
  • Stage نهایی برای اجرای برنامه

این روش باعث می‌شود ابزارها و فایل‌های موقتی مرحله Build وارد Image نهایی نشوند. نتیجه معمولاً Image مرتب‌تر و قابل‌کنترل‌تری است.

استفاده از Multi-stage Build برای همه پروژه‌ها الزامی نیست، اما در پروژه‌های واقعی می‌تواند ساختار Build و Runtime را بهتر از یکدیگر جدا کند.

مرحله هفتم: ساخت .dockerignore

فایل .dockerignore تعیین می‌کند چه فایل‌هایی وارد Build Context نشوند:

.git
.gitignore

.env
.env.*
!.env.example

.venv
venv
__pycache__
*.pyc
*.pyo

.pytest_cache
.mypy_cache
.ruff_cache

.idea
.vscode

dist
build
*.log

README.md
compose.yaml

فایل .dockerignore با .gitignore تفاوت دارد:

  • .gitignore از ثبت فایل در Git جلوگیری می‌کند.
  • .dockerignore از ارسال فایل به Docker Build Context جلوگیری می‌کند.

وجود .env در هر دو فایل مهم است؛ زیرا نباید کلید API وارد Git یا Docker Image شود.

مرحله هشتم: ساخت .gitignore

فایل .gitignore:

.env
.env.*
!.env.example

.venv/
venv/
__pycache__/
*.py[cod]

.pytest_cache/
.mypy_cache/
.ruff_cache/

.idea/
.vscode/

dist/
build/
*.log

مرحله نهم: ساخت compose.yaml

فایل compose.yaml:

name: darvareh-docker-ai-api

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile

    env_file:
      - .env

    environment:
      REDIS_URL: redis://redis:6379/0

    ports:
      - "127.0.0.1:8000:8000"

    depends_on:
      redis:
        condition: service_healthy

    restart: unless-stopped
    init: true
    read_only: true

    tmpfs:
      - /tmp

  redis:
    image: redis:7-alpine

    command:
      - redis-server
      - --appendonly
      - "yes"

    volumes:
      - redis_data:/data

    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 5s

    restart: unless-stopped

volumes:
  redis_data:

بررسی تنظیمات Compose

قبل از اجرا، ساختار فایل را اعتبارسنجی کنید:

docker compose config

این دستور فایل Compose را پردازش می‌کند و خطاهای ساختاری یا متغیرهای ناقص را نشان می‌دهد.

توجه کنید که خروجی docker compose config ممکن است مقادیر متغیرهای پردازش‌شده را نمایش دهد. خروجی آن را بدون بررسی در Issue، Log عمومی یا پیام گروهی منتشر نکنید.

ساخت و اجرای پروژه

ساخت Image و اجرای سرویس‌ها:

docker compose up --build

برای اجرا در پس‌زمینه:

docker compose up --build -d

طبق راهنمای دستور docker compose up، این فرمان سرویس‌های تعریف‌شده را می‌سازد، ایجاد می‌کند و اجرا می‌کند.

مشاهده وضعیت:

docker compose ps

نمونه سرویس‌های مورد انتظار:

app
redis

آزمایش API

بررسی Health Endpoint:

curl http://127.0.0.1:8000/health

پاسخ مورد انتظار:

{
  "status": "ok",
  "redis": "ok"
}

ارسال درخواست به مدل:

curl -X POST http://127.0.0.1:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Docker را برای یک برنامه‌نویس مبتدی توضیح بده",
    "temperature": 0.3,
    "max_tokens": 600
  }'

پاسخ اول احتمالاً شامل این مقدار است:

{
  "answer": "پاسخ مدل...",
  "model": "YOUR_MODEL_ID",
  "cached": false
}

اگر همان درخواست را دوباره و پیش از پایان TTL ارسال کنید:

{
  "answer": "پاسخ مدل...",
  "model": "YOUR_MODEL_ID",
  "cached": true
}

مقدار cached: true نشان می‌دهد پاسخ از Redis خوانده شده است.

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

اجرای درخواست در PowerShell

$body = @{
    message = "Docker Compose چیست؟"
    temperature = 0.3
    max_tokens = 600
} | ConvertTo-Json

Invoke-RestMethod `
    -Method Post `
    -Uri "http://127.0.0.1:8000/chat" `
    -ContentType "application/json" `
    -Body $body

مشاهده Logهای Container

نمایش Log تمام سرویس‌ها:

docker compose logs

دنبال‌کردن زنده Logها:

docker compose logs -f

مشاهده Log فقط برای برنامه:

docker compose logs -f app

نمایش ۱۰۰ خط آخر:

docker compose logs --tail=100 app

برای خروج از حالت دنبال‌کردن Log می‌توانید Ctrl+C را بزنید. اگر سرویس‌ها با -d اجرا شده باشند، این کار Containerها را متوقف نمی‌کند.

ورود به Container

اجرای Shell داخل Container برنامه:

docker compose exec app sh

مشاهده فایل‌های داخل آن:

ls -la

خروج:

exit

اجرای یک فرمان بدون ورود تعاملی:

docker compose exec app python --version

اجرای Redis CLI:

docker compose exec redis redis-cli

داخل Redis CLI:

PING
DBSIZE
SCAN 0

خروج:

QUIT

در محیط Production از فرمان‌های سنگین مانند دریافت یک‌جای تمام کلیدهای Redis روی پایگاه داده بزرگ استفاده نکنید.

توقف و حذف Containerها

توقف سرویس‌ها:

docker compose stop

اجرای دوباره:

docker compose start

توقف و حذف Containerها و Network پروژه:

docker compose down

Volume با دستور بالا حذف نمی‌شود؛ بنابراین Cache یا داده پایدار Redis باقی می‌ماند.

حذف Containerها به همراه Volumeها:

docker compose down -v

گزینه -v داده‌های Volume پروژه را حذف می‌کند. پیش از اجرای آن مطمئن شوید اطلاعات موردنیاز یا داده Production داخل Volume وجود ندارد.

مدیریت Docker Image

مشاهده Imageها:

docker image ls

ساخت Image با Tag مشخص:

docker build -t darvareh-ai-api:1.0.0 .

اجرای مستقیم بدون Compose:

docker run \
  --rm \
  --name darvareh-ai-api \
  --env-file .env \
  -e REDIS_URL="" \
  -p 127.0.0.1:8000:8000 \
  darvareh-ai-api:1.0.0

پروژه فعلی برای Cache به Redis طراحی شده است، اما در نبود Redis نیز می‌تواند درخواست مدل را پردازش کند. در استفاده مستقیم، خطای اتصال Redis توسط برنامه نادیده گرفته می‌شود.

مشاهده تاریخچه لایه‌های Image:

docker history darvareh-ai-api:1.0.0

مشاهده اطلاعات کامل Image:

docker image inspect darvareh-ai-api:1.0.0

Docker Volume در پروژه چگونه کار می‌کند؟

در فایل Compose این بخش را داریم:

volumes:
  - redis_data:/data

و در انتهای فایل:

volumes:
  redis_data:

Docker یک Named Volume می‌سازد و آن را به مسیر /data در Container مربوط به Redis متصل می‌کند.

مشاهده Volumeها:

docker volume ls

بررسی Volume پروژه:

docker volume inspect darvareh-docker-ai-api_redis_data

نام دقیق Volume ممکن است بر اساس نام پروژه Compose متفاوت باشد.

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

Docker Network در پروژه چگونه کار می‌کند؟

Compose برای سرویس‌های پروژه یک Network پیش‌فرض می‌سازد.

سرویس app می‌تواند با این آدرس به Redis دسترسی داشته باشد:

redis://redis:6379/0

در این آدرس:

  • بخش اول redis نام پروتکل است.
  • بخش دوم redis نام سرویس در Compose است.
  • عدد 6379 پورت داخلی Redis است.
  • عدد 0 شماره Database منطقی Redis است.

نباید از localhost برای اتصال app به Redis استفاده کنیم؛ زیرا داخل Container برنامه، localhost به همان Container برنامه اشاره می‌کند، نه Container Redis.

تفاوت پورت داخلی و پورت میزبان

در Compose نوشته‌ایم:

ports:
  - "127.0.0.1:8000:8000"

ساختار کلی:

HOST_IP:HOST_PORT:CONTAINER_PORT

پورت داخلی FastAPI برابر 8000 است و همان پورت روی آدرس محلی سیستم میزبان در دسترس قرار می‌گیرد.

اتصال به 127.0.0.1 باعث می‌شود سرویس در این نمونه فقط از همان سیستم میزبان قابل دسترسی باشد. اگر قصد انتشار عمومی دارید، معمولاً یک Reverse Proxy مانند Nginx در مقابل برنامه قرار می‌گیرد و TLS، Domain و محدودیت‌های دسترسی در آن تنظیم می‌شوند.

Redis را در بخش ports منتشر نکرده‌ایم، زیرا فقط برنامه داخل Network پروژه باید به آن دسترسی داشته باشد.

Healthcheck چیست؟

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

ممکن است Process اجرا شده باشد اما برنامه هنوز آماده پذیرش درخواست نباشد. Healthcheck این تفاوت را مشخص می‌کند.

در Compose برای Redis نوشته‌ایم:

healthcheck:
  test:
    - CMD
    - redis-cli
    - ping

برنامه نیز با این شرط منتظر سالم‌شدن Redis می‌ماند:

depends_on:
  redis:
    condition: service_healthy

وجود depends_on بدون Healthcheck لزوماً به معنای آماده‌بودن کامل سرویس وابسته نیست. راهنمای رسمی ترتیب شروع Compose نیز برای مدیریت آمادگی سرویس‌ها از Healthcheck و شرط service_healthy استفاده می‌کند.

به‌روزرسانی کد و بازسازی Container

اگر app.py را تغییر دادید:

docker compose up -d --build app

مشاهده Log:

docker compose logs -f app

اگر فقط فایل Compose تغییر کرده باشد:

docker compose up -d

Compose وضعیت جدید را با سرویس‌های موجود تطبیق می‌دهد و بخش‌های لازم را دوباره ایجاد می‌کند.

استفاده از Bind Mount در محیط توسعه

برای اینکه تغییر فایل app.py بدون Build مجدد داخل Container دیده شود، می‌توانید یک فایل توسعه مانند compose.dev.yaml بسازید:

services:
  app:
    volumes:
      - ./app.py:/app/app.py:ro

    command:
      - uvicorn
      - app:app
      - --host
      - 0.0.0.0
      - --port
      - "8000"
      - --reload

اجرا:

docker compose \
  -f compose.yaml \
  -f compose.dev.yaml \
  up --build

این تنظیم برای توسعه محلی مناسب است. در Production بهتر است کد داخل Image قرار بگیرد و از --reload استفاده نشود.

متغیر محیطی و Secret چه تفاوتی دارند؟

در پروژه نمونه، متغیرها از فایل .env خوانده می‌شوند. این روش برای توسعه محلی ساده است، اما فایل .env نباید وارد Git یا Image شود.

برای بررسی:

git check-ignore -v .env

بررسی فایل‌های Build Context:

docker build --no-cache -t darvareh-ai-api:test .

کلید API را با ARG یا ENV ثابت داخل Dockerfile قرار ندهید:

ENV DARVAREH_API_KEY=کلید-واقعی

این کار مناسب نیست، زیرا مقدار ممکن است وارد لایه‌ها، تاریخچه Build یا Image شود.

برای محیط Production از قابلیت مدیریت Secret پلتفرم استقرار یا Secret Manager استفاده کنید. همچنین دسترسی هر Secret را فقط به سرویس نیازمند آن محدود کنید.

اگر کلید API افشا شد، حذف آن از فایل به‌تنهایی کافی نیست؛ کلید را در پنل مربوط باطل یا تعویض کنید.

کاهش اندازه Docker Image

برای کوچک‌تر و سریع‌ترشدن Image:

  • از Base Image متناسب با نیاز استفاده کنید.
  • فایل .dockerignore کامل داشته باشید.
  • Cache، فایل‌های Git و محیط مجازی را کپی نکنید.
  • وابستگی‌های غیرضروری نصب نکنید.
  • Build و Runtime را در Stageهای جدا انجام دهید.
  • فایل‌های موقتی Package Manager را نگه ندارید.
  • ترتیب دستورها را برای استفاده بهتر از Build Cache تنظیم کنید.

برای مثال، ابتدا requirements.txt را کپی کرده‌ایم و بعد از نصب وابستگی‌ها، app.py را کپی می‌کنیم. در نتیجه تغییر کد الزاماً Cache لایه نصب وابستگی‌ها را بی‌اعتبار نمی‌کند.

ترتیب لایه‌ها و Build Cache

هر دستور مهم در Dockerfile یک لایه ایجاد می‌کند. اگر یک لایه تغییر کند، لایه‌های بعد از آن معمولاً دوباره ساخته می‌شوند.

ترتیب مناسب:

COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .

ترتیب کمتر بهینه:

COPY . .
RUN pip install -r requirements.txt

در حالت دوم، تغییر کوچک در فایل کد می‌تواند باعث اجرای دوباره نصب تمام وابستگی‌ها شود.

برای ساخت بدون Cache:

docker compose build --no-cache

این فرمان برای عیب‌یابی مفید است، اما در استفاده روزمره زمان Build را افزایش می‌دهد.

Tag مناسب برای Image

از Tagهایی مانند latest به‌تنهایی برای انتشارهای مهم استفاده نکنید. Tag نسخه‌دار امکان ردیابی و Rollback دقیق‌تر را فراهم می‌کند:

docker build -t darvareh-ai-api:1.0.0 .
docker build -t darvareh-ai-api:1.0.1 .

می‌توانید شناسه Commit را نیز وارد Tag کنید:

docker build -t darvareh-ai-api:a1b2c3d .

در CI/CD مقدار شناسه معمولاً به‌صورت خودکار از Git دریافت می‌شود.

اجرای چند نمونه از سرویس

برای آزمایش محلی می‌توانید تعداد Containerهای سرویس app را افزایش دهید:

docker compose up -d --scale app=3

اما اگر در Compose برای سرویس app یک پورت ثابت میزبان تعریف شده باشد، چند Container نمی‌توانند هم‌زمان همان پورت را تصاحب کنند. برای Scale واقعی باید Load Balancer یا Reverse Proxy در مقابل نمونه‌ها قرار گیرد و تنظیم Port نیز متناسب با آن طراحی شود.

Docker Compose برای محیط تک‌سرور و توسعه بسیار کاربردی است، اما Scale چندسروری معمولاً به ابزارها و معماری دیگری نیاز دارد.

تفاوت restart و rebuild

Restart فقط Container موجود را دوباره اجرا می‌کند:

docker compose restart app

اگر کد داخل Image تغییر کرده باشد، Restart تغییر جدید را وارد Image نمی‌کند. ابتدا باید Image بازسازی شود:

docker compose up -d --build app

قاعده ساده:

  • تغییر تنظیم Runtime: ممکن است Recreate کافی باشد.
  • تغییر کد کپی‌شده داخل Image: نیازمند Build جدید است.
  • توقف موقت Process: Restart کافی است.

دستورات کاربردی Docker

مشاهده Containerهای در حال اجرا

docker ps

مشاهده تمام Containerها

docker ps -a

مشاهده Imageها

docker image ls

مشاهده Volumeها

docker volume ls

مشاهده Networkها

docker network ls

مشاهده مصرف منابع

docker stats

بررسی جزئیات Container

docker inspect CONTAINER_NAME

مشاهده Processهای Container

docker top CONTAINER_NAME

توقف Container

docker stop CONTAINER_NAME

شروع دوباره Container

docker start CONTAINER_NAME

حذف Container متوقف‌شده

docker rm CONTAINER_NAME

حذف Image مشخص

docker image rm IMAGE_NAME:TAG

پیش از حذف Image یا Volume مطمئن شوید به آن نیاز ندارید و Container وابسته‌ای وجود ندارد.

خطاهای رایج Docker و راه‌حل آن‌ها

خطای Cannot connect to the Docker daemon

نمونه:

Cannot connect to the Docker daemon

موارد زیر را بررسی کنید:

  • Docker Desktop یا Docker Engine اجرا شده باشد.
  • کاربر اجازه ارتباط با Docker daemon داشته باشد.
  • Docker Context درست انتخاب شده باشد.
  • سرویس Docker متوقف نشده باشد.

بررسی Context:

docker context ls

بررسی اطلاعات Engine:

docker info

خطای Port is already allocated

نمونه:

port is already allocated

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

در Compose پورت میزبان را تغییر دهید:

ports:
  - "127.0.0.1:8080:8000"

حالا API از این آدرس در دسترس است:

http://127.0.0.1:8080

خطای ModuleNotFoundError

احتمالاً کتابخانه موردنیاز داخل requirements.txt نیست یا Image بعد از تغییر وابستگی‌ها بازسازی نشده است.

docker compose build --no-cache app
docker compose up -d app

برنامه نمی‌تواند به Redis متصل شود

داخل Container برنامه از localhost استفاده نکنید. آدرس صحیح بر اساس نام سرویس است:

redis://redis:6379/0

وضعیت Redis:

docker compose ps redis

Log آن:

docker compose logs redis

آزمایش:

docker compose exec redis redis-cli ping

پاسخ مورد انتظار:

PONG

متغیر محیطی در دسترس نیست

بررسی کنید فایل .env در مسیر اجرای Compose وجود داشته باشد:

ls -la

در PowerShell:

Get-ChildItem -Force

مشاهده نام متغیرهای داخل Container بدون نمایش مقدارهای محرمانه:

docker compose exec app python -c \
"import os; print('DARVAREH_API_KEY' in os.environ)"

پاسخ باید True باشد.

تغییر کد اعمال نمی‌شود

اگر کد داخل Image کپی شده باشد، آن را بازسازی کنید:

docker compose up -d --build app

سپس Container و Image استفاده‌شده را بررسی کنید:

docker compose ps
docker compose images

Container بلافاصله متوقف می‌شود

Log را مشاهده کنید:

docker compose logs --tail=100 app

پیکربندی نهایی Compose را بررسی کنید:

docker compose config

فرمان اجرای Image را نیز ببینید:

docker image inspect darvareh-docker-ai-api-app

اشتباهات رایج هنگام استفاده از Docker

قرار دادن کلید API داخل Dockerfile

Secret را داخل Dockerfile یا Image ذخیره نکنید. آن را هنگام اجرای Container از محیط امن دریافت کنید.

استفاده دائمی از latest

Tag نسخه‌دار امکان تشخیص Image و بازگشت به نسخه قبلی را آسان‌تر می‌کند.

انتشار مستقیم Port پایگاه داده

اگر فقط سرویس داخلی به Redis یا Database نیاز دارد، Port آن را روی اینترنت یا حتی میزبان منتشر نکنید.

اجرای غیرضروری برنامه با Root

در صورت امکان، داخل Image کاربر محدود بسازید و برنامه را با آن اجرا کنید.

نگهداری داده داخل لایه Container

برای داده پایدار از Volume یا سرویس ذخیره‌سازی مناسب استفاده کنید.

ساخت Image بسیار بزرگ

فایل‌های غیرضروری، Cacheها، Datasetها، وزن مدل‌ها و محیط مجازی محلی را داخل Image کپی نکنید.

استفاده از Docker به‌جای Backup

Volume باعث پایداری داده می‌شود، اما جایگزین Backup نیست. برای اطلاعات مهم باید برنامه پشتیبان‌گیری و بازیابی جداگانه داشته باشید.

استفاده از docker compose down -v بدون بررسی

گزینه -v می‌تواند Volumeهای پروژه و داده داخل آن‌ها را حذف کند. قبل از اجرا نام و اهمیت Volumeها را بررسی کنید.

چک‌لیست آماده‌سازی پروژه Docker

پیش از Build:

  • Dockerfile وجود دارد.
  • .dockerignore ساخته شده است.
  • .env وارد Build Context نمی‌شود.
  • وابستگی‌ها مشخص‌اند.
  • کلید API داخل کد نیست.
  • Health Endpoint وجود دارد.

پس از Build:

  • Image بدون خطا ساخته می‌شود.
  • Container با کاربر غیر Root اجرا می‌شود.
  • Healthcheck موفق است.
  • Logها اطلاعات محرمانه ندارند.
  • Portهای غیرضروری منتشر نشده‌اند.
  • API به مدل درواره متصل می‌شود.
  • Redis از داخل برنامه قابل دسترسی است.
  • Cache در صورت قطع Redis مانع عملکرد اصلی نمی‌شود.

پیش از انتشار:

  • نسخه Image مشخص است.
  • تنظیمات Production جدا شده‌اند.
  • Secretها از سامانه امن دریافت می‌شوند.
  • TLS و Reverse Proxy تنظیم شده‌اند.
  • احراز هویت API فعال است.
  • محدودیت نرخ درخواست در نظر گرفته شده است.
  • Timeout و Retry کنترل‌شده‌اند.
  • Backup داده‌های پایدار تعریف شده است.
  • مانیتورینگ و مشاهده Logها برقرار است.
  • مسیر Rollback آزمایش شده است.

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

Docker برای چه کسانی مناسب است؟

Docker برای توسعه‌دهندگان Backend، Frontend، DevOps، Data Engineering، Machine Learning و تیم‌هایی مناسب است که می‌خواهند برنامه را در محیط‌های مختلف به‌صورت سازگار اجرا کنند.

آیا برای یادگیری Docker باید Linux بلد باشیم؟

دانستن مفاهیم پایه Linux و خط فرمان مفید است، اما برای شروع الزامی نیست. بسیاری از دستورات Docker در ویندوز، macOS و Linux یکسان‌اند.

آیا Docker رایگان است؟

Docker Engine متن‌باز است، اما بعضی محصولات و قابلیت‌های تجاری Docker ممکن است شرایط یا پلن‌های متفاوتی داشته باشند. برای تصمیم سازمانی، شرایط جاری محصول موردنظر را در وب‌سایت رسمی بررسی کنید.

آیا Docker برای اجرای مدل هوش مصنوعی مناسب است؟

بله، به‌خصوص برای بسته‌بندی API مدل، Worker، پردازش داده و سرویس‌های جانبی. بااین‌حال، اجرای مدل‌های بزرگ محلی ممکن است به GPU، Driver، Runtime سازگار و فضای ذخیره‌سازی زیادی نیاز داشته باشد.

در پروژه این مقاله، مدل روی سرور محلی اجرا نمی‌شود؛ برنامه از طریق API درواره به مدل انتخابی متصل می‌شود. بنابراین Image برنامه کوچک‌تر است و به وزن مدل محلی نیاز ندارد.

آیا باید وزن مدل‌ها را داخل Docker Image قرار دهیم؟

معمولاً وزن مدل‌های بزرگ را مستقیم داخل Image عمومی قرار نمی‌دهند. این فایل‌ها می‌توانند بسیار حجیم باشند و فرایند Build، Push و Pull را کند کنند. روش مناسب به زیرساخت، مجوز مدل و شیوه استقرار بستگی دارد.

تفاوت docker run و docker compose up چیست؟

docker run معمولاً برای اجرای مستقیم یک Container استفاده می‌شود. docker compose up مجموعه‌ای از سرویس‌ها، Networkها و Volumeهای تعریف‌شده در فایل Compose را مدیریت می‌کند.

آیا حذف Container باعث حذف Image می‌شود؟

خیر. Container و Image منابع جداگانه‌اند. حذف Container معمولاً Image آن را حذف نمی‌کند.

آیا حذف Container باعث حذف Volume می‌شود؟

معمولاً Named Volume با حذف Container باقی می‌ماند، مگر آنکه صریحاً دستور حذف Volume اجرا شود.

چرا داخل Container نمی‌توانم با localhost به Redis وصل شوم؟

زیرا localhost داخل Container برنامه به خود همان Container اشاره می‌کند. برای اتصال به سرویس دیگر در Compose باید از نام سرویس، مانند redis، استفاده کنید.

آیا Docker Compose برای Production مناسب است؟

Docker Compose برای توسعه، آزمایش و استقرارهای تک‌سرور بسیار کاربردی است. برای Production باید محدودیت‌ها، Availability، Backup، مدیریت Secret، به‌روزرسانی، Rollback و Scale پروژه را جداگانه بررسی کنید.

آیا Docker امنیت برنامه را تضمین می‌کند؟

خیر. Docker ابزار جداسازی و بسته‌بندی برنامه است، اما امنیت نهایی به Imageها، تنظیمات دسترسی، Secretها، Network، سیستم میزبان، به‌روزرسانی‌ها و معماری برنامه نیز وابسته است.

آیا می‌توان API درواره را از داخل Docker فراخوانی کرد؟

بله. کافی است Container به اینترنت دسترسی داشته باشد و Base URL و کلید API به‌درستی تنظیم شده باشند:

client = OpenAI(
    api_key=os.getenv("DARVAREH_API_KEY"),
    base_url="https://api.darvareh.ir/v1",
)

جمع‌بندی

Docker روشی استاندارد برای بسته‌بندی و اجرای برنامه‌ها فراهم می‌کند. با Docker می‌توانید کد، Runtime و وابستگی‌های برنامه را داخل Image قرار دهید و از روی آن Containerهای قابل‌تکرار بسازید.

در این آموزش با مفاهیم زیر آشنا شدیم:

  • Docker Image
  • Docker Container
  • Dockerfile
  • Docker Compose
  • Volume
  • Network
  • Port Mapping
  • Healthcheck
  • Build Cache
  • Environment Variable
  • اجرای چند سرویس کنار یکدیگر

همچنین یک پروژه عملی ساختیم که شامل FastAPI، Redis و اتصال به API هوش مصنوعی درواره بود. این پروژه می‌تواند پایه‌ای برای ساخت دستیار سازمانی، تولیدکننده محتوا، ابزار خلاصه‌سازی، سرویس پردازش متن یا قابلیت هوش مصنوعی داخل نرم‌افزار شما باشد.

برای شروع، در درواره ثبت‌نام کنید، کلید API بگیرید و شناسه مدل مناسب پروژه را از صفحه مدل‌های درواره انتخاب کنید.

منابع تکمیلی

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

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

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.