Skip to content

النشر باستخدام Docker Compose

يُنشئ FORGE إعدادات Docker Compose جاهزة للإنتاج مع Caddy كـ reverse proxy وHTTPS تلقائي عبر Let's Encrypt وجميع خدمات التطبيق مُحوّاة. هذه هي طريقة النشر المُوصى بها لإعدادات الخادم الواحد والتطبيقات الصغيرة إلى المتوسطة.

توليد إعدادات الإنتاج

bash
forge deploy:compose --env=production --ssl=letsencrypt

هذا يُنشئ الملفات التالية:

deploy/
├── docker-compose.prod.yaml    # ملف Compose للإنتاج
├── Caddyfile                   # إعدادات reverse proxy لـ Caddy
└── .env.production             # متغيرات البيئة

بيئة التجربة

أنشئ إعدادات بيئة التجربة بتغيير علم --env:

bash
forge deploy:compose --env=staging --ssl=letsencrypt

الخدمات المُولّدة

ملف Compose للإنتاج يُعرّف ست خدمات:

┌──────────────────────────────────────────────────────────┐
│                  DOCKER COMPOSE STACK                     │
├──────────────────────────────────────────────────────────┤
│                                                          │
│   الإنترنت                                               │
│      │                                                   │
│      ▼                                                   │
│   ┌────────────────┐                                     │
│   │     Caddy      │  :80, :443                          │
│   │  (reverse      │  HTTPS تلقائي عبر Let's Encrypt     │
│   │   proxy)       │                                     │
│   └──────┬─────────┘                                     │
│          │                                               │
│    ┌─────┼────────────────┐                              │
│    │     │                │                              │
│    ▼     ▼                ▼                              │
│ ┌──────┐ ┌──────┐  ┌──────────┐                         │
│ │ web  │ │ api  │  │  admin   │                         │
│ │:3000 │ │:8080 │  │  :3001   │                         │
│ └──────┘ └──┬───┘  └──────────┘                         │
│             │                                            │
│       ┌─────┴─────┐                                     │
│       │           │                                     │
│       ▼           ▼                                     │
│  ┌──────────┐ ┌────────┐                                │
│  │ postgres │ │ redis  │                                │
│  │  :5432   │ │ :6379  │                                │
│  └──────────┘ └────────┘                                │
│                                                          │
└──────────────────────────────────────────────────────────┘

ملف Docker Compose

ملف docker-compose.prod.yaml المُولّد:

yaml
version: '3.8'

services:
  api:
    image: ${REGISTRY:-docker.io}/${APP_NAME}-api:${VERSION:-latest}
    restart: always
    environment:
      - DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379
      - FORGE_ENCRYPTION_KEY=${FORGE_ENCRYPTION_KEY}
      - RUST_LOG=info
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - internal
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

  web:
    image: ${REGISTRY:-docker.io}/${APP_NAME}-web:${VERSION:-latest}
    restart: always
    environment:
      - NEXT_PUBLIC_API_URL=https://api.${DOMAIN}
      - API_URL=http://api:8080
    depends_on:
      - api
    networks:
      - internal

  admin:
    image: ${REGISTRY:-docker.io}/${APP_NAME}-admin:${VERSION:-latest}
    restart: always
    environment:
      - NEXT_PUBLIC_API_URL=https://api.${DOMAIN}
      - API_URL=http://api:8080
    depends_on:
      - api
    networks:
      - internal

  caddy:
    image: caddy:2-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"   # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - web
      - admin
      - api
    networks:
      - internal
      - external

  postgres:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - internal

volumes:
  postgres_data:
  redis_data:
  caddy_data:
  caddy_config:

networks:
  internal:
    driver: bridge
  external:
    driver: bridge

Caddyfile (HTTPS تلقائي)

Caddy يحصل تلقائياً على شهادات Let's Encrypt ويُجددها. لا حاجة لإعدادات SSL يدوية.

{
    email admin@example.com
}

myapp.com {
    reverse_proxy web:3000
    encode gzip
}

admin.myapp.com {
    reverse_proxy admin:3001
    encode gzip
}

api.myapp.com {
    reverse_proxy api:8080
    encode gzip

    header {
        Access-Control-Allow-Origin https://myapp.com
        Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        Access-Control-Allow-Headers "Authorization, Content-Type"
    }
}

كيف يتعامل Caddy مع HTTPS

Caddy يحصل تلقائياً على شهادات TLS من Let's Encrypt عند استخدام أسماء نطاقات حقيقية. يتعامل مع تجديد الشهادات قبل انتهاء صلاحيتها وإعادة توجيه HTTP إلى HTTPS وOCSP stapling -- كل ذلك بدون أي إعدادات. المتطلب الوحيد هو أن يُشير DNS نطاقك إلى الخادم وأن تكون المنافذ 80 و443 متاحة.

إعدادات البيئة

ملف .env.production المُولّد:

bash
# Application
APP_NAME=myapp
DOMAIN=myapp.com
VERSION=latest
REGISTRY=docker.io

# Database
DB_NAME=myapp
DB_USER=myapp
DB_PASSWORD=          # Set a strong password
DB_HOST=postgres
DB_PORT=5432

# Security
JWT_SECRET=           # Generate: openssl rand -hex 32
FORGE_ENCRYPTION_KEY= # Generate: forge secrets:generate-key

# Caddy
CADDY_EMAIL=admin@myapp.com

عيّن جميع الأسرار قبل النشر

لا تنشر أبداً بقيم فارغة أو افتراضية لـ DB_PASSWORD أو JWT_SECRET أو FORGE_ENCRYPTION_KEY. أنشئ قيم قوية وفريدة لكل منها.

بيئة التجربة مقابل الإنتاج

الإعدادالتجربةالإنتاج
DOMAINstaging.myapp.commyapp.com
RUST_LOGdebuginfo
DB_POOL_SIZE520
RATE_LIMIT_RPM1000100
VERSIONstaging-latestv1.0.0

استمرارية البيانات

جميع البيانات الدائمة تُخزّن في Docker volumes مُسمّاة:

Volumeالخدمةالبيانات
postgres_dataPostgreSQLملفات قاعدة البيانات والجداول والفهارس
redis_dataRedisالذاكرة المؤقتة والجلسات والمهام المؤجلة
caddy_dataCaddyشهادات TLS واستجابات OCSP
caddy_configCaddyالإعدادات أثناء التشغيل

انسخ الـ volumes احتياطياً

Docker volumes تستمر عبر إعادة تشغيل الحاويات وتحديثات الصور، لكنها لا تُنسخ احتياطياً تلقائياً. أعدّ نسخ احتياطية منتظمة لـ postgres_data على الأقل.

bash
# Backup PostgreSQL data
docker exec myapp-postgres-1 \
  pg_dump -U myapp myapp > backup_$(date +%Y%m%d).sql

# Restore from backup
docker exec -i myapp-postgres-1 \
  psql -U myapp myapp < backup_20260127.sql

البدء والإدارة

النشر الأولي

bash
# Navigate to deploy directory
cd deploy/

# Set environment variables
cp .env.production .env
# Edit .env and set all secrets

# Build and start all services
docker compose -f docker-compose.prod.yaml up -d

# Verify all services are running
docker compose -f docker-compose.prod.yaml ps

# Run database migrations
docker compose -f docker-compose.prod.yaml exec api \
  ./forge-cli migrate

# Seed default data
docker compose -f docker-compose.prod.yaml exec api \
  ./forge-cli seed

العمليات الشائعة

bash
# All services
docker compose -f docker-compose.prod.yaml logs -f

# Specific service
docker compose -f docker-compose.prod.yaml logs -f api
docker compose -f docker-compose.prod.yaml logs -f caddy
bash
# Restart single service
docker compose -f docker-compose.prod.yaml restart api

# Restart all services
docker compose -f docker-compose.prod.yaml restart
bash
# Pull new images
docker compose -f docker-compose.prod.yaml pull

# Recreate containers with new images (zero downtime)
docker compose -f docker-compose.prod.yaml up -d --no-deps api
docker compose -f docker-compose.prod.yaml up -d --no-deps web
docker compose -f docker-compose.prod.yaml up -d --no-deps admin

# Run migrations after update
docker compose -f docker-compose.prod.yaml exec api \
  ./forge-cli migrate
bash
# Stop all services (preserves data)
docker compose -f docker-compose.prod.yaml down

# Stop and remove volumes (destroys data)
docker compose -f docker-compose.prod.yaml down -v

فحوصات الصحة

تحقق أن نشرك سليم:

bash
# Check service health
docker compose -f docker-compose.prod.yaml ps

# Test API health endpoint
curl https://api.myapp.com/health

# Test web application
curl -I https://myapp.com

# Test admin application
curl -I https://admin.myapp.com

# Verify SSL certificate
curl -vI https://myapp.com 2>&1 | grep "SSL certificate"

بناء صور Docker

يُنشئ FORGE ملفات Dockerfile لكل خدمة. ابنِ وادفع الصور قبل النشر:

bash
# Build all images
docker build -t myapp-api:latest ./apps/api
docker build -t myapp-web:latest ./apps/web
docker build -t myapp-admin:latest ./apps/admin
bash
# Tag and push to your registry
docker tag myapp-api:latest registry.example.com/myapp-api:v1.0.0
docker push registry.example.com/myapp-api:v1.0.0

docker tag myapp-web:latest registry.example.com/myapp-web:v1.0.0
docker push registry.example.com/myapp-web:v1.0.0

docker tag myapp-admin:latest registry.example.com/myapp-admin:v1.0.0
docker push registry.example.com/myapp-admin:v1.0.0

الخطوات التالية

Released under the MIT License.