استخدام Docker Compose لتطوير ونشر تطبيقات Django

استخدام Docker Compose لتطوير ونشر تطبيقات Django

عندما تبدأ مشروع Django جديدًا، يبدو كل شيء بسيطًا جدًا في البداية: تثبيت Python، إنشاء virtual environment، تشغيل pip install، ثم تنفيذ python manage.py runserver والانطلاق في التطوير. لكن مع الوقت، ومع دخول قواعد البيانات، وخدمات التخزين المؤقت، ومهام الخلفية، وملفات البيئة، واختلاف أجهزة الفريق، تبدأ المشاكل الصغيرة بالتحول إلى فوضى حقيقية. تعمل عندك ولا تعمل عند زميلك. قاعدة البيانات مختلفة. نسخة Python مختلفة. إعدادات البيئة لا تتطابق. ثم يأتي يوم النشر، فتكتشف أن ما كان يعمل محليًا لا يعمل على الخادم بنفس السلاسة.

هنا يظهر Docker Compose كأحد أفضل الحلول العملية، ليس لأنه “موضة تقنية”، بل لأنه يختصر الكثير من الوقت ويجعل المشروع أكثر انضباطًا. الفكرة ببساطة أنك لا تعود تعتمد على تثبيت كل شيء يدويًا على جهازك أو على السيرفر، بل تصف البيئة كاملة داخل ملفات واضحة: خدمة Django، خدمة PostgreSQL، خدمة Redis، وربما Nginx أو Celery أو أي خدمة أخرى يحتاجها مشروعك. ثم يقوم Docker Compose بتشغيل هذه الخدمات معًا بطريقة متناسقة.

هذا المقال يشرح الفكرة من الصفر، ثم ينتقل إلى بناء مشروع Django حقيقي باستخدام Docker Compose، ثم يوضح كيف تنتقل من بيئة التطوير إلى بيئة النشر بشكل عملي وواقعي. لن يكون الكلام نظريًا فقط، بل سنمشي خطوة بخطوة مع أمثلة كود واضحة، وبأسلوب قريب من تجربة المطور اليومية، لأن أفضل ما في Docker Compose ليس أنه “أداة قوية” فقط، بل أنه يجعل حياتك أبسط بكثير عندما يُستخدم بالطريقة الصحيحة.

ما هو Docker Compose ولماذا تحتاجه مع Django؟

Docker Compose هو أداة تسمح لك بتشغيل عدة حاويات Docker معًا من خلال ملف YAML واحد غالبًا اسمه docker-compose.yml أو compose.yml. بدل أن تبدأ كل خدمة يدويًا بأوامر منفصلة ومعقدة، تقوم بكتابة وصف كامل للخدمات، المنافذ، المتغيرات البيئية، الشبكات، والأحجام التخزينية، ثم تشغل المشروع كله بأمر واحد.

في تطبيق Django نموذجي، قد تحتاج إلى:

  • تطبيق Django نفسه.

  • قاعدة بيانات PostgreSQL.

  • Redis لاستخدامه مع Celery أو caching.

  • عامل خلفية مثل Celery Worker.

  • Scheduler مثل Celery Beat.

  • Nginx كخادم وسيط في الإنتاج.

  • أحيانًا خدمة لإدارة ملفات media أو S3 أو MinIO في بيئات الاختبار.

استخدام Docker Compose هنا لا يقتصر على “تشغيل الخدمات”. هو في الحقيقة يحقق لك ثلاثة أشياء مهمة جدًا: توحيد البيئة بين جميع المطورين، تسهيل التشغيل المحلي، وتبسيط النشر. وهذا ما يجعله مناسبًا جدًا لتطبيقات Django، لأن Django بطبيعته يتعامل مع عدة مكونات خارجية ويستفيد من البنية الواضحة والمنظمة.

الأجمل من ذلك أن Docker Compose لا يلزمك أن تتحول إلى خبير DevOps من اليوم الأول. يمكنك أن تبدأ بتركيب بسيط جدًا ثم توسع المشروع تدريجيًا. تبدأ بـ Django و PostgreSQL فقط، ثم تضيف Redis، ثم Celery، ثم Nginx، ثم تحسن النشر خطوة خطوة. هذا التدرج هو ما يجعل الأداة ممتازة للمشاريع الصغيرة والمتوسطة وحتى المشاريع التي تنمو لاحقًا.

لماذا Docker Compose مناسب جدًا لتطوير Django؟

Django ممتاز كإطار عمل، لكنه لا يعيش وحده في معظم المشاريع الحديثة. غالبًا ما تعتمد عليه مع قاعدة بيانات، وربما مع خدمة كاش، وربما مع مهام غير متزامنة، وربما مع خوادم ملفات أو أدوات مراقبة. هنا يأتي Docker Compose ليجمع هذه البيئة في صورة واحدة.

من الناحية العملية، أنت عندما تعمل بدون Docker Compose تكون مضطرًا إلى تكرار أشياء كثيرة: تثبيت PostgreSQL، إعداد المستخدمين، ضبط الإصدارات، تنصيب Redis، التأكد من أن Python و pip و system packages متوافقة، ثم التعامل مع اختلافات نظام التشغيل. أما باستخدام Compose، فكل هذه الطبقات تُكتب في ملفات، ويمكن لأي شخص في الفريق أن يبدأ المشروع في دقائق معدودة.

هناك أيضًا فائدة نفسية مهمة لا يتحدث عنها الكثيرون: عندما تكون البيئة موثقة ومُعرفة بالكود، يصبح المشروع أقل اعتمادًا على “الذاكرة الجماعية”. لا يعود السؤال: “ماذا يجب أن أثبت على هذا الجهاز؟” بل يصبح السؤال: “ما الذي تقوله ملفات المشروع؟”. وهذا أفضل بكثير لصحة المشروع على المدى الطويل.

بنية المشروع التي سنبنيها

سنفترض مشروعًا بسيطًا لكنه واقعي. هيكل المجلدات سيكون تقريبًا بهذا الشكل:

project-root/
├── app/
│   ├── manage.py
│   ├── requirements.txt
│   ├── Dockerfile
│   ├── entrypoint.sh
│   ├── .env
│   ├── config/
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── ...
├── docker-compose.yml
├── docker-compose.prod.yml
└── .gitignore

قد تختلف التسمية من مشروع لآخر، لكن الفكرة هنا هي فصل كود Django داخل مجلد خاص، ووضع ملفات البنية التحتية في الجذر. هذا التنظيم يسهل الصيانة لاحقًا، خاصة عندما يكبر المشروع وتبدأ بإضافة خدمات جديدة.

إعداد Django داخل Docker

أول خطوة هي تجهيز التطبيق نفسه داخل حاوية. عادةً نستخدم Dockerfile داخل مجلد التطبيق. هذا الملف يصف كيف يتم بناء صورة Docker الخاصة بتطبيق Django.

مثال على Dockerfile للتطوير والإنتاج الأساسي

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

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 /app/
RUN pip install --upgrade pip && pip install -r requirements.txt

COPY . /app/

RUN chmod +x /app/entrypoint.sh

ENTRYPOINT ["/app/entrypoint.sh"]

هذا المثال يعتمد على صورة Python خفيفة نسبيًا. نثبت بعض الحزم المطلوبة مثل libpq-dev لأننا سنستخدم PostgreSQL. ثم ننسخ ملفات المتطلبات ونثبتها، وبعد ذلك ننسخ بقية المشروع. وأخيرًا نحدد entrypoint.sh كنقطة بداية عند تشغيل الحاوية.

ملف entrypoint.sh ولماذا هو مهم؟

كثيرون يتجاهلون entrypoint.sh، لكنه عملي جدًا. فكرته أن هناك أوامر تريد تنفيذها تلقائيًا كلما بدأت الحاوية: مثل انتظار قاعدة البيانات حتى تصبح جاهزة، ثم تنفيذ migrations، ثم جمع الملفات الثابتة، ثم تشغيل السيرفر.

مثال على entrypoint.sh

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for PostgreSQL..."

    while ! nc -z db 5432; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi

python manage.py migrate --noinput

exec "$@"

هذا المثال بسيط، لكنه يوضح الفكرة الأساسية. عندما يبدأ التطبيق، لا يفترض أن قاعدة البيانات جاهزة في نفس اللحظة. لذلك ننتظر وصولها ثم ننفذ migrations. وفي النهاية نمرر الأمر النهائي الذي سيشغل التطبيق، مثل Gunicorn أو runserver.

قد يبدو هذا تفصيلًا صغيرًا، لكنه في الواقع يوفر عليك أخطاء مزعجة جدًا من نوع “connection refused” أو “database not ready” في أول تشغيل.

ملف المتطلبات requirements.txt

في Docker، ملف المتطلبات ليس مجرد قائمة مكتبات، بل هو جزء أساسي من قابلية إعادة البناء. مثال بسيط:

Django>=5.1,<6.0
psycopg2-binary>=2.9
gunicorn>=22.0
whitenoise>=6.7
python-dotenv>=1.0
redis>=5.0
celery>=5.4

قد تحتاج أيضًا إلى مكتبات مثل djangorestframework أو django-cors-headers أو أي حزمة خاصة بمشروعك. المهم أن يكون الملف واضحًا ومضبوطًا، لأن هذا ما سيستخدمه Docker أثناء البناء.

إعداد Django لقراءة متغيرات البيئة

من أفضل الممارسات ألا تكتب بيانات الاتصال مباشرة داخل settings.py. بدل ذلك استخدم متغيرات بيئية. هذا يجعل المشروع أكثر أمانًا وأسهل في النقل بين التطوير والإنتاج.

مثال على ملف .env

DEBUG=1
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=localhost,127.0.0.1

DATABASE_NAME=django_db
DATABASE_USER=django_user
DATABASE_PASSWORD=django_password
DATABASE_HOST=db
DATABASE_PORT=5432

REDIS_URL=redis://redis:6379/0

مثال على settings.py

import os
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.getenv("SECRET_KEY", "unsafe-secret-key")
DEBUG = os.getenv("DEBUG", "0") == "1"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.getenv("DATABASE_NAME"),
        "USER": os.getenv("DATABASE_USER"),
        "PASSWORD": os.getenv("DATABASE_PASSWORD"),
        "HOST": os.getenv("DATABASE_HOST", "db"),
        "PORT": os.getenv("DATABASE_PORT", "5432"),
    }
}

STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

هذا الأسلوب يمنحك مرونة كبيرة. في التطوير قد تستخدم قيمًا محلية، وفي الإنتاج تغيرها إلى قيم الخادم الفعلية، دون تعديل الكود نفسه.

بناء خدمة PostgreSQL مع Docker Compose

الخطوة التالية هي إضافة قاعدة البيانات. في مشاريع Django الحديثة، PostgreSQL هو الاختيار الأكثر شيوعًا لأنه قوي، متين، ويدعم كثيرًا من ميزات الاستخدام الواقعي.

مثال docker-compose.yml للتطوير

services:
  web:
    build: ./app
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    ports:
      - "8000:8000"
    env_file:
      - ./app/.env
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: django_db
      POSTGRES_USER: django_user
      POSTGRES_PASSWORD: django_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  static_volume:
  media_volume:

هذا الملف يشرح خدمتين: خدمة web وهي Django، وخدمة db وهي PostgreSQL. استخدمنا volumes لثلاثة أسباب: الاحتفاظ ببيانات PostgreSQL، حفظ static files، وحفظ media files. بهذه الطريقة لا تضيع البيانات كل مرة يعاد فيها تشغيل الحاويات.

وجود depends_on لا يعني أن قاعدة البيانات أصبحت جاهزة بالكامل قبل التشغيل، لكنه يساعد على ترتيب الإقلاع. ولهذا يبقى entrypoint.sh مهمًا أيضًا.

تشغيل المشروع لأول مرة

بعد تجهيز الملفات، يمكنك تشغيل المشروع بالأمر:

docker compose up --build

إذا كانت هذه أول مرة، سيبني Docker الصورة، ثم يبدأ الخدمات. بعد ذلك يمكنك تنفيذ أوامر Django المعتادة داخل الحاوية.

إنشاء المشروع أو تنفيذ أوامر الإدارة

docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
docker compose exec web python manage.py createsuperuser

هذا من أجمل ما في Docker Compose: أنت لا تحتاج إلى الدخول اليدوي إلى الحاوية كل مرة. فقط استخدم exec ونفذ ما تريد.

لماذا نستخدم volumes؟

الـ volumes من أهم أجزاء Docker Compose، خصوصًا في Django. بدونها ستفقد بيانات قاعدة البيانات عند إعادة البناء، وربما ستفقد الملفات المرفوعة أو الملفات الثابتة التي تحتاجها في التطوير. عندما تفهم الـ volumes جيدًا، يصبح Docker أقل “غموضًا” بكثير.

في المثال السابق، لدينا:

  • postgres_data لتخزين بيانات PostgreSQL.

  • static_volume لتخزين الملفات الثابتة.

  • media_volume لتخزين الملفات المرفوعة.

في التطوير، أحيانًا يكفي mount مباشر لمجلد المشروع داخل الحاوية حتى تظهر التغييرات فورًا. هذا يوفر تجربة قريبة من العمل المحلي التقليدي، لكن مع مزايا الحاويات.

إدخال Redis و Celery إلى المعادلة

كثير من تطبيقات Django تحتاج مهام غير متزامنة، مثل إرسال البريد الإلكتروني، معالجة الصور، توليد التقارير، أو تنفيذ مهام مجدولة. هنا يأتي Celery، وغالبًا يحتاج Redis كـ broker.

مثال docker-compose.yml مع Redis و Celery

services:
  web:
    build: ./app
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    ports:
      - "8000:8000"
    env_file:
      - ./app/.env
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: django_db
      POSTGRES_USER: django_user
      POSTGRES_PASSWORD: django_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7
    ports:
      - "6379:6379"

  celery:
    build: ./app
    command: celery -A config worker -l info
    volumes:
      - ./app:/app
    env_file:
      - ./app/.env
    depends_on:
      - db
      - redis

  celery-beat:
    build: ./app
    command: celery -A config beat -l info
    volumes:
      - ./app:/app
    env_file:
      - ./app/.env
    depends_on:
      - db
      - redis

volumes:
  postgres_data:
  static_volume:
  media_volume:

في هذا التكوين، لديك تطبيق الويب، قاعدة البيانات، Redis، وعامل Celery، بالإضافة إلى Celery Beat. هذه البنية مناسبة جدًا لتطبيقات Django التي تعتمد على المهام الخلفية. والأجمل أنك ترى كل الخدمات في ملف واحد، بدل أن تنسى تشغيل خدمة هنا أو هناك.

إعداد Celery داخل Django

حتى يكتمل المشهد، تحتاج إلى ربط Celery مع Django بشكل صحيح.

مثال celery.py

import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")

app = Celery("config")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

في init.py

from .celery import app as celery_app

__all__ = ("celery_app",)

في settings.py

CELERY_BROKER_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"

مثال task بسيط

from celery import shared_task

@shared_task
def send_welcome_email(user_id):
    print(f"Sending welcome email to user {user_id}")

بهذه الخطوات يصبح Django جاهزًا للتعامل مع المهام غير المتزامنة عبر Docker Compose. وهذا مهم جدًا في المشاريع التي تتطلب استجابة سريعة للطلبات وتفريغ الأعمال الثقيلة إلى الخلفية.

استخدام Gunicorn بدل runserver في الإنتاج

الـ runserver رائع للتطوير، لكنه ليس خادم إنتاج. في بيئة النشر، من الأفضل استخدام Gunicorn مع Nginx أو أي reverse proxy آخر. Gunicorn هو خادم WSGI مناسب لتشغيل Django في الإنتاج بشكل أفضل من الخادم الداخلي.

مثال أمر التشغيل الإنتاجي

gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3

عدد العمال workers يعتمد على حجم الخادم وعدد الأنوية، لكنه غالبًا يبدأ بقيمة مناسبة مثل 2 أو 3 أو أكثر قليلًا حسب الموارد.

إضافة Nginx في النشر

عندما تنشر Django على الإنترنت، تحتاج غالبًا إلى Nginx ليكون الواجهة الأمامية التي تستقبل الطلبات، توزعها، وتخدم static و media files بكفاءة أكبر. في هذا السيناريو، لا يُفضل أن يكون Django هو الخادم المباشر للزوار.

مثال docker-compose.prod.yml

services:
  web:
    build:
      context: ./app
      dockerfile: Dockerfile
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3
    env_file:
      - ./app/.env.prod
    depends_on:
      - db
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: django_db
      POSTGRES_USER: django_user
      POSTGRES_PASSWORD: strongpassword
    volumes:
      - postgres_data:/var/lib/postgresql/data

  nginx:
    image: nginx:1.27
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    depends_on:
      - web

volumes:
  postgres_data:
  static_volume:
  media_volume:

مثال nginx/default.conf

server {
    listen 80;
    server_name _;

    location /static/ {
        alias /app/staticfiles/;
    }

    location /media/ {
        alias /app/media/;
    }

    location / {
        proxy_pass http://web:8000;
        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;
    }
}

هذه البنية شائعة جدًا لأنها تفصل المسؤوليات: Django يعالج منطق التطبيق، وNginx يعتني بالطلبات الخارجية والملفات الثابتة وتوجيه الحركة.

الفرق بين بيئة التطوير وبيئة الإنتاج

من الأخطاء الشائعة أن يستخدم المطور نفس إعدادات التطوير في الإنتاج. هذا ينجح أحيانًا في البداية، لكنه ليس جيدًا على المدى الطويل. بيئة التطوير تحتاج إلى سرعة في التعديل وسهولة في إعادة التشغيل، أما بيئة الإنتاج فتحتاج إلى ثبات، أمان، وسلوك متوقع.

في التطوير، قد تستخدم:

  • runserver

  • mount مباشر للكود

  • DEBUG=True

  • منافذ مفتوحة للمساعدة في الاختبار

  • إعادة تشغيل تلقائية

أما في الإنتاج، فغالبًا تستخدم:

  • gunicorn

  • nginx

  • DEBUG=False

  • ملفات إعدادات منفصلة

  • كلمات مرور قوية

  • إعدادات أمان إضافية

  • فصل بين services بشكل أوضح

النجاح الحقيقي مع Docker Compose لا يعني أن يكون الملف واحدًا فقط، بل أن تكون هناك استراتيجية واضحة: ملف للتطوير، وملف للإنتاج، مع متغيرات مختلفة حسب السياق.

نصائح لتنظيم ملفات Compose

كلما كبر المشروع، يصبح ملف Compose طويلًا. لذلك من المفيد أن تتعامل معه كجزء من الكود، وليس كملف جانبي. هناك عدة أفكار تساعدك:

1) افصل التطوير عن الإنتاج

استخدم:

  • docker-compose.yml للتطوير

  • docker-compose.prod.yml للإنتاج

أو compose.override.yml عند الحاجة إلى override محلي.

2) سمِّ الخدمات بوضوح

استخدم أسماء مثل web, db, redis, celery, nginx. الأسماء الواضحة تختصر كثيرًا في القراءة والتشخيص.

3) احتفظ بملفات البيئة خارج Git

لا تضع .env الحقيقي داخل المستودع. ضع .env.example فقط، واجعل النسخ الفعلية محفوظة بأمان.

4) لا تكثر من المنافذ المفتوحة بلا داعٍ

في التطوير قد تفتح PostgreSQL أو Redis على جهازك، لكن في الإنتاج غالبًا لا تحتاج إلى تعريضها للعالم الخارجي.

5) راقب ترتيب الإقلاع

الخدمات قد تبدأ بترتيب معين، لكن الجاهزية الفعلية ليست دائمًا فورية. لذلك تأكد أن تطبيق Django ينتظر قاعدة البيانات عند الحاجة.

مثال إعداد أكثر واقعية لمشروع Django حديث

لنفترض مشروعًا يضم Django وPostgreSQL وRedis وCelery وNginx. يمكن أن يكون التكوين المنطقي كالتالي:

docker-compose.yml

services:
  web:
    build: ./app
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    ports:
      - "8000:8000"
    env_file:
      - ./app/.env
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: django_db
      POSTGRES_USER: django_user
      POSTGRES_PASSWORD: django_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7

  celery:
    build: ./app
    command: celery -A config worker -l info
    volumes:
      - ./app:/app
    env_file:
      - ./app/.env
    depends_on:
      - db
      - redis

volumes:
  postgres_data:
  static_volume:
  media_volume:

docker-compose.prod.yml

services:
  web:
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    env_file:
      - ./app/.env.prod

  nginx:
    image: nginx:1.27
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    depends_on:
      - web

هذا التقسيم يجعلك قادرًا على التطوير بسرعة، ثم النشر بثقة أكبر، دون أن تعيد كتابة كل شيء من الصفر.

كيف تتعامل مع Static Files و Media Files؟

في Django، الملفات الثابتة والملفات المرفوعة تشكل نقطة حساسة في Docker. في التطوير قد تبدو الأمور سهلة، لكن في الإنتاج يجب أن تكون واضحة.

إعداد static في Django

STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

ثم في النشر أو قبل تشغيل Nginx:

python manage.py collectstatic --noinput

إعداد media

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

وفي Nginx، يتم توجيه /media/ إلى المسار المناسب داخل الحاوية أو على الخادم.

الفكرة الأساسية هنا أن Django لا ينبغي أن يخدم الملفات الثابتة بنفسه في الإنتاج، بل تُترك هذه المهمة لـ Nginx أو خادم ملفات متخصص.

هل Docker Compose يكفي وحده للنشر؟

السؤال الشائع جدًا هو: هل Docker Compose مناسب للإنتاج؟ والإجابة هي: نعم، في كثير من الحالات، لكنه ليس الحل الوحيد. بالنسبة للتطبيقات الصغيرة والمتوسطة، أو حتى بعض التطبيقات المتوسطة الكبيرة إذا كانت البنية واضحة، يمكن لـ Docker Compose أن يكون كافيًا جدًا. لكن عندما تكبر المنظومة جدًا وتحتاج إلى auto-scaling متقدم، ومراقبة دقيقة، وتوزيع خدمات معقد، قد تنتقل لاحقًا إلى Kubernetes أو حلول orchestration أخرى.

مع ذلك، لا تقلل من قيمة Docker Compose. الكثير من المشاريع الناجحة تبدأ به وتبقى عليه لفترة طويلة، لأنه بسيط، مفهوم، وسريع، ويعطي توازنًا ممتازًا بين القوة والسهولة.

أخطاء شائعة يجب تجنبها

هناك أخطاء متكررة يقع فيها المطورون عند استخدام Docker Compose مع Django، وأشهرها:

1) نسيان إعدادات البيئة

استخدام قيم مباشرة داخل الإعدادات أو ترك .env ناقصًا يسبب أخطاء مربكة.

2) تشغيل runserver في الإنتاج

هذا غير مناسب للإنتاج. استخدم Gunicorn أو uWSGI.

3) عدم استخدام volumes

قد يخسر المشروع بياناته أو الملفات المهمة عند إعادة التشغيل.

4) اعتبار depends_on كافيًا

هو لا يضمن أن قاعدة البيانات جاهزة بالكامل. تحتاج أحيانًا إلى الانتظار أو إعادة المحاولة.

5) بناء صورة ثقيلة بلا داعٍ

الصورة الكبيرة تبطئ النشر وتستهلك موارد أكثر.

6) خلط إعدادات التطوير والإنتاج

هذا يؤدي إلى سلوك غير متوقع ومشاكل أمان.

تحسين أداء الصور Docker

الأداء مهم حتى لو بدا الأمر “تفصيلًا تقنيًا”. الصورة الأخف تعني بناء أسرع، ونشر أسرع، وتشغيلًا أكثر سلاسة.

بعض النصائح العملية:

  • استخدم صورًا خفيفة مثل python:3.12-slim.

  • احذف الحزم المؤقتة بعد التثبيت.

  • لا تنسخ ملفات غير ضرورية داخل الصورة.

  • استخدم .dockerignore لتقليل حجم الـ build context.

  • اجعل الطبقات أكثر استقرارًا بتثبيت المتطلبات قبل نسخ الكود الكامل إن أمكن.

مثال .dockerignore

__pycache__
*.pyc
*.pyo
*.pyd
.env
venv
.git
.gitignore
node_modules
media
staticfiles

هذا الملف الصغير يوفر كثيرًا من الوقت ويمنع نسخ ملفات لا حاجة لها داخل الصورة.

مثال تشخيص سريع للمشاكل

أحيانًا تواجه مشاكل أثناء التشغيل، لكن Docker Compose يجعل التشخيص أسهل عندما تعرف من أين تبدأ.

مشاهدة السجلات

docker compose logs -f web
docker compose logs -f db
docker compose logs -f celery

الدخول إلى الحاوية

docker compose exec web bash

التأكد من أن قاعدة البيانات تعمل

docker compose ps

إعادة البناء من الصفر

docker compose down -v
docker compose up --build

هذا مفيد عندما تريد تنظيف البيئة وإعادة التجربة من البداية.

تنظيم الفريق باستخدام Docker Compose

في فرق العمل، Docker Compose يزيل الكثير من الخلافات الصغيرة التي تستهلك الوقت. بدل أن يقضي أحدهم نصف يومه في ضبط PostgreSQL، وآخر في حل مشكلة Python، وثالث في تعديل Redis، يصبح هناك تعريف واحد واضح للبيئة. والنتيجة أن الجميع يركز على الميزة نفسها بدل الانشغال بالبنية التحتية.

من الناحية العملية، يمكن للفريق أن يعتمد على خطوات بسيطة جدًا:

  1. استنساخ المستودع.

  2. نسخ .env.example إلى .env.

  3. تشغيل docker compose up --build.

  4. تنفيذ migrations.

  5. بدء التطوير.

هذه البساطة ترفع الإنتاجية بشكل ملحوظ، خاصة في المشاريع التي يدخلها مطورون جدد باستمرار.

متى يكون Docker Compose خيارًا ممتازًا؟

Docker Compose مناسب جدًا عندما يكون لديك واحد أو أكثر من السيناريوهات التالية:

  • مشروع Django يحتاج PostgreSQL أو Redis.

  • فريق صغير أو متوسط.

  • بيئة تطوير محلية يجب أن تكون قابلة للتكرار.

  • تطبيق تريد نشره على VPS أو خادم واحد.

  • رغبة في تنظيم الخدمات دون التعقيد الزائد.

  • حاجة حقيقية إلى فصل الخدمات وتوثيقها.

أما إذا كنت تبني بنية موزعة ضخمة جدًا مع عشرات الخدمات، فحينها قد تحتاج لاحقًا إلى أدوات أكبر. لكن هذا لا يلغي دور Compose، بل يجعله مرحلة مهمة جدًا في رحلة التطور التقني.

نموذج عملي كامل ومختصر

فيما يلي نموذج مبسط يجمع العناصر الأساسية معًا.

Dockerfile

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

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

COPY requirements.txt /app/
RUN pip install --upgrade pip && pip install -r requirements.txt

COPY . /app/
RUN chmod +x /app/entrypoint.sh

ENTRYPOINT ["/app/entrypoint.sh"]

entrypoint.sh

#!/bin/sh

echo "Waiting for database..."
while ! nc -z db 5432; do
  sleep 0.2
done

python manage.py migrate --noinput
exec "$@"

docker-compose.yml

services:
  web:
    build: ./app
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
    ports:
      - "8000:8000"
    env_file:
      - ./app/.env
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: django_db
      POSTGRES_USER: django_user
      POSTGRES_PASSWORD: django_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

settings.py

import os
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.getenv("SECRET_KEY", "unsafe-secret-key")
DEBUG = os.getenv("DEBUG", "0") == "1"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.getenv("DATABASE_NAME", "django_db"),
        "USER": os.getenv("DATABASE_USER", "django_user"),
        "PASSWORD": os.getenv("DATABASE_PASSWORD", "django_password"),
        "HOST": os.getenv("DATABASE_HOST", "db"),
        "PORT": os.getenv("DATABASE_PORT", "5432"),
    }
}

هذا النموذج الصغير يمكن أن يكون نقطة انطلاق ممتازة، ثم تضيف إليه Redis وCelery وNginx وملفات الإنتاج عندما يحتاج المشروع لذلك.

منظور عملي: لماذا يشعر المطور بالراحة مع Docker Compose؟

السبب ليس فقط تقنيًا، بل إنساني أيضًا. المطور يحب أن يشعر أن البيئة تحت السيطرة. أن يكون قادرًا على إعادة المشروع للعمل في أي وقت. أن لا يفاجأه خطأ “كان يعمل أمس”. أن يكون لديه مكان واحد يرى فيه الصورة الكبيرة. Docker Compose يوفر هذا الإحساس بالوضوح.

وعندما تتكرر التجربة نفسها عند كل عضو في الفريق، يصبح التواصل أسهل، والتجربة أكثر استقرارًا، والمشروع أقل عرضة للفوضى. وهذه قيمة كبيرة جدًا في عالم التطوير، حتى لو لم تُذكر كثيرًا في العناوين الكبيرة.

خاتمة

استخدام Docker Compose مع Django ليس مجرد تحسين جانبي، بل هو أسلوب عملي لبناء مشاريع أكثر تنظيمًا ووضوحًا واستعدادًا للنمو. في التطوير، يمنحك بيئة متكررة وسهلة التشغيل. وفي النشر، يساعدك على بناء تركيب منطقي يجمع Django وPostgreSQL وRedis وNginx وغيرها من الخدمات داخل منظومة واحدة مفهومة. ومع الوقت ستكتشف أن فائدته الحقيقية لا تكمن فقط في تشغيل الحاويات، بل في تقليل العشوائية داخل المشروع، وتوحيد التجربة بين المطورين، وجعل الانتقال من المحلي إلى الإنتاج أقل توترًا وأكثر سلاسة.

إذا كنت تبني مشروع Django جادًا، فتعلم Docker Compose مبكرًا سيختصر عليك الكثير من الوقت لاحقًا. ليس لأنك تحتاج “تعقيدًا” أكثر، بل لأنك تحتاج نظامًا أبسط وأوضح وأكثر قابلية للتوسع. وهذه بالضبط هي الفكرة التي تجعل Compose يستحق أن يكون جزءًا أساسيًا من أدواتك اليومية.

كلمات أخيرة للمطور

في البداية قد يبدو كل شيء جديدًا قليلًا: Dockerfile، volumes، services، entrypoint، Nginx، Gunicorn، Redis، Celery. لكن مع أول مشروع حقيقي، ستلاحظ أن الصورة بدأت تتضح. وبعد مشروع أو اثنين، لن تعود ترى Docker Compose كعبء إضافي، بل كقاعدة مريحة تعود إليها كلما أردت أن تبني مشروع Django بشكل نظيف من البداية.

الجميل في هذه الأدوات أنها لا تأخذ منك الإبداع، بل تحميه من التفاصيل المتكررة. وهذا بالضبط ما نحتاجه كمطورين: أن نصرف وقتنا في بناء القيمة الحقيقية، لا في مطاردة إعدادات البيئة كل مرة من جديد.

#Docker Compose #Django #Django Docker #نشر تطبيقات Django #تطوير Django #PostgreSQL #Redis #Gunicorn #Nginx #حاويات Docker #بيئة تطوير #بيئة إنتاج #إدارة الخدمات #Python #DevOps #deployment

اشترك في نشرتنا البريدية

12k+

المشتركون

أسبوعيًا

التكرار

مجاني

دائمًا