المرحلة 5: النشر والإطلاق

النشر في بيئة الإنتاج

3 دقيقة للقراءة

لقد بنيت TaskFlow من الصفر -- النماذج والترحيلات ونقاط النهاية والمصادقة والاختبارات. حان الوقت الآن لتعبئته في حاوية جاهزة للإنتاج وإطلاقه.

بناء Docker متعدد المراحل

البناء متعدد المراحل يفصل بيئة البناء (حيث تثبت التبعيات وتجمّع) عن بيئة التشغيل (ما يعمل فعلياً في الإنتاج). هذا ينتج صوراً أصغر وأكثر أماناً.

# المرحلة 1: البناء -- تثبيت التبعيات
FROM python:3.13-slim AS builder

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential libpq-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# المرحلة 2: الإنتاج -- بيئة تشغيل خفيفة
FROM python:3.13-slim AS production

RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 curl \
    && rm -rf /var/lib/apt/lists/*

# مستخدم غير جذري للأمان
RUN groupadd -r taskflow && useradd -r -g taskflow taskflow

WORKDIR /app

# نسخ الحزم المثبتة فقط من مرحلة البناء
COPY --from=builder /install /usr/local
COPY . .

RUN chown -R taskflow:taskflow /app
USER taskflow

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

EXPOSE 8000
CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]

المبادئ الأساسية:

المبدألماذا هو مهم
بناء متعدد المراحلمرحلة البناء تُحذف؛ الصورة النهائية تحتوي فقط على تبعيات التشغيل
تثبيت إصدار الصورة الأساسيةpython:3.13-slim بدلاً من python:latest يمنع الأعطال المفاجئة
مستخدم غير جذرييحد من الضرر إذا تم اختراق الحاوية
HEALTHCHECKالمُنسقات (Compose، ECS، K8s) تعرف متى يكون التطبيق جاهزاً فعلاً
ترتيب الطبقاتCOPY requirements.txt قبل COPY . لتُخزن طبقات التبعيات مؤقتاً

Docker Compose للإنتاج

احتفظ بملفات منفصلة للتطوير والإنتاج. التطوير يستخدم إعادة التحميل التلقائي وإعدادات التصحيح؛ الإنتاج يستخدم حدود الموارد وسياسات إعادة التشغيل.

# docker-compose.prod.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    ports:
      - "8000:8000"
    env_file: .env.production
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
    restart: unless-stopped
    networks:
      - external
      - internal

  db:
    image: postgres:18
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: taskflow
      POSTGRES_USER: taskflow
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U taskflow"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal

  redis:
    image: redis:8-alpine
    command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - internal

volumes:
  pgdata:
  redisdata:

networks:
  external:
  internal:
    internal: true

ما الذي يُبعد قاعدة البيانات عن الإنترنت فعلاً

يغري المرء أن يقرأ internal: true على أنها «ما يجعل Postgres خاصاً». وهي ليست كذلك بمفردها — وسوء قراءتها هو كيف تنتهي قاعدة بيانات إلى الإنصات على عنوان IP عام. آليتان مستقلتان تقومان بالعمل في ذلك الملف، وأنت بحاجة إليهما معاً:

آليتان منفصلتان لتهديدين مختلفين

الوارد — يحكمه نشر المنافذ
الصادر — تحكمه internal: true
نمط الفشل الجدير بالتذكر

إذن: ports: تقرر من يصل إليك، وinternal: true تقرر إن كانت الحاويات تستطيع الوصول للخارج. وليست إحداهما بديلاً عن الأخرى. والعادة الجديرة بالتكوين أن تقرأ ملف Compose بطرح هذين السؤالين منفصلين على كل خدمة، بدل البحث عن عَلَم واحد معناه «آمن».

إدارة البيئة

لا تُضمّن الأسرار أبداً في الكود. استخدم ملفات .env للتطوير المحلي ومخازن الأسرار المناسبة للإنتاج.

# .env.development (يُرفع للمستودع كـ .env.example بقيم فارغة)
DATABASE_URL=postgresql+asyncpg://taskflow:localpass@localhost:5432/taskflow
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=dev-only-not-for-production
ENVIRONMENT=development

# .env.production (لا يُرفع أبداً -- استخدم أسرار CI/CD)
DATABASE_URL=postgresql+asyncpg://taskflow:${DB_PASSWORD}@db:5432/taskflow
REDIS_URL=redis://redis:6379/0
SECRET_KEY=${SECRET_KEY}
ENVIRONMENT=production

مع Pydantic Settings، يتم التحقق من التكوين عند بدء التشغيل:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    redis_url: str
    secret_key: str
    environment: str = "development"
    api_v1_prefix: str = "/api/v1"

    @property
    def is_production(self) -> bool:
        return self.environment == "production"

    model_config = {"env_file": ".env"}

settings = Settings()

CI/CD مع GitHub Actions

خط أنابيب قياسي: اختبار، بناء، نشر -- يُشغل عند كل دفع إلى main وعند طلبات السحب.

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:18
        env:
          POSTGRES_DB: taskflow_test
          POSTGRES_USER: taskflow
          POSTGRES_PASSWORD: testpassword
        ports: ["5432:5432"]
        options: >-
          --health-cmd="pg_isready -U taskflow"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5
      redis:
        image: redis:8-alpine
        ports: ["6379:6379"]
        options: >-
          --health-cmd="redis-cli ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - run: pip install -r requirements.txt
      - run: ruff check .
      - run: pytest --cov=app tests/
        env:
          DATABASE_URL: postgresql+asyncpg://taskflow:testpassword@localhost:5432/taskflow_test
          REDIS_URL: redis://localhost:6379/0
          SECRET_KEY: test-secret-key

  build:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t taskflow-api:${{ github.sha }} .

قائمة فحص الإنتاج

قبل الإطلاق، تحقق من كل عنصر:

الفئةالعنصرالكيفية
الأمانمستخدم Docker غير جذريUSER taskflow في Dockerfile
الأمانترويسات الأمانMiddleware: X-Content-Type-Options، X-Frame-Options، Strict-Transport-Security
الأمانCORS مقيدأصول مسموحة صريحة، لا تستخدم * أبداً في الإنتاج
الأداءعمال Gunicorngunicorn -w 4 -k uvicorn.workers.UvicornWorker
الموثوقيةفحوصات الصحةنقطة /health تُرجع 200 عندما يكون DB وRedis متاحين
الموثوقيةسياسة إعادة التشغيلrestart: unless-stopped في Compose
الإصداراتبادئة APIجميع المسارات تحت /api/v1/
الأسرارلا مفاتيح مُضمنةجميع الأسرار من متغيرات البيئة أو مدير الأسرار

اختيار مكان للتشغيل

لأنك بنيت حاوية لا تطبيقاً مرتبطاً بمنصة بعينها، فهذا القرار قابل للتراجع — وهذا معظم سبب استخدام الحاويات أصلاً. أي مستضيف يشغّل صورة OCI سيشغّل TaskFlow.

وبدل جدول من المستويات والأسعار يصير خاطئاً خلال ربع سنة، إليك الأسئلة التي تحسم الأمر فعلاً، مرتبةً بحسب أثرها:

السؤاللماذا يحسم الجواب
من يدير قاعدة البيانات؟قاعدة Postgres مُدارة بنسخ احتياطي آلي واستعادة إلى نقطة زمنية هي أكبر فارق تشغيلي منفرد بين المستضيفين. وتشغيلها بنفسك في حاوية مقبول حتى أول عطل قرص.
ما خطتك في الاستعادة؟اسأل تحديداً: كيف أستعيد الحالة إلى الثالثة فجر أمس، وهل جرّبت ذلك؟ منصة بنشر بضغطة واحدة وبلا استعادة مُجرَّبة رهانٌ أسوأ من منصة أعقد لكن بنسخ احتياطي تدرّبت عليه.
هل تحتاج أكثر من منطقة جغرافية؟تعدد المناطق فارق حقيقي بين المستضيفين وتكلفة حقيقية أيضاً. معظم الواجهات لا تحتاجه، ومن يحتاجه يعرف سببه عادةً.
هل يفرض شيء شبكة VPC أو حدوداً تنظيمية؟هذا ما يدفع الفرق إلى AWS أو GCP أو Azure بغض النظر عن سهولة الاستخدام. وإن انطبق فهو يتقدم على كل اعتبار آخر.
كيف يبدو الخروج؟إن كان الانتقال يعني إعادة كتابة إعدادات النشر فقط فأنت بخير. أما إن كان يعني إعادة كتابة كود التطبيق فقد ربطت نفسك بالمستضيف.

من المنصات الشائعة لهذا النوع من التطبيقات: Railway وRender وFly.io وAWS ECS/Fargate. ومستوياتها المجانية ومناطقها وعروض قواعد البيانات المُدارة تتغير بوتيرة تكفي لأن يصير أي رقم مكتوب هنا خاطئاً قبل أن تقرأه — وهذه الروابط هي مصدر الحقيقة. التسعير تحديداً شيء ينبغي أن تفحصه دائماً على صفحة المزوّد نفسه لا في درس تعليمي.

التالي: معمل عملي حيث ستُحوّل TaskFlow إلى حاوية وتُعد خط أنابيب CI/CD الكامل. :::

اختبار

اختبار الوحدة 5: النشر والإطلاق

خذ الاختبار
هل كان هذا الدرس مفيدًا؟

سجّل الدخول للتقييم