العودة إلى المدونة
أغسطس 19, 2026الأدلة

كيفية نشر التطبيقات بـ Docker Compose على VPS

دليل إنتاج عملي لـ Docker Compose v2 على Ubuntu: ثبّت المحرك، واكتب ملف Compose بشبكات وأحجام، وتعامل مع الأسرار، وضع وكيلًا عكسيًا أمامها، وحدّث بطرح بلا ذعر، وانسخ الأحجام المسمّاة احتياطيًا.

كيفية نشر التطبيقات بـ Docker Compose على VPS

Docker Compose هو الطريق المعتاد لتشغيل حزمة صغيرة على VPS واحد: تطبيق ويب وقاعدة بيانات وذاكرة Redis مؤقتة ووكيل عكسي، كلها في ملف YAML واحد. ليس Kubernetes. لن تحصل على تجاوز فشل متعدد العقد. ستحصل على إعداد قابل للتكرار والمراجعة تستطيع إعادة إنشائه على VPS جديد من Hiddence في دقائق — وهو بالضبط ما تحتاجه معظم المشاريع الجانبية والمنتجات الصغيرة.

تفترض هذه المقالة أنك تريد شيئًا أقرب للإنتاج من docker run ليلة الجمعة. سنثبّت Docker Engine وإضافة Compose، وننشئ مستخدم نشر غير root، ونكتب ملف Compose بفحوصات صحة وسياسات إعادة تشغيل، ونبقي الأسرار خارج Git، وننشر منافذ الوكيل العكسي فقط إلى الإنترنت، ونعرّف روتين تحديث ونسخ احتياطي. الحزمة المثال تطبيق ويب نموذجي مع PostgreSQL وCaddy، لكن النمط نفسه يعمل لـ Node أو PHP أو Python أو مجموعة عمال.

لماذا ما زال Compose على VPS واحد منطقيًا

يقفز الناس إلى Kubernetes لأنه رائج ثم يقضون عطلة نهاية الأسبوع في YAML لموقع واحد. يبقى Compose قابلًا للقراءة. تُصدِّر الملف وتوثّق متغيرات البيئة ويمكنك مقارنة تغيير قبل تطبيقه. العزل كافٍ: حاوية تطبيق مخترقة لا ينبغي أن تمسك بمقبس UNIX لقاعدة البيانات إن استخدمت شبكات ومستخدمين بأقل امتياز. حدود الموارد توقف تسرب ذاكرة واحدًا من تجميد VPS كله.

  • ملف واحد يصف الحزمة كلها
  • الأحجام المسمّاة تبقى بعد إعادة إنشاء الحاويات
  • شبكات Docker الداخلية تُبقي Postgres بعيدًا عن الإنترنت العام
  • restart: unless-stopped يغطي معظم عمليات إعادة التشغيل
  • سهل النسخ إلى VPS ثانٍ عندما تتجاوز الأول

المتطلبات

استخدم VPS بذاكرة كافية للتطبيق مع Postgres. خطة 2 غيغابايت حد أدنى واقعي لتطبيق + قاعدة بيانات + وكيل. يُفترض Ubuntu 24.04. لا تثبّت Docker من Snap عشوائي دون قراءة ما يفعله بـ cgroups؛ الخطوات أدناه تستخدم مستودع apt الرسمي من Docker.

  • Ubuntu 22.04/24.04، root أو sudo
  • يُنصح بـ 2 غيغابايت RAM (غيغابايت واحد فقط للحزم الصغيرة جدًا بلا Postgres)
  • نطاق يشير إلى VPS إن أردت HTTPS تلقائيًا
  • Git أو طريقة أخرى لنسخ المشروع إلى الخادم

الخطوة 1: ثبّت Docker Engine وCompose v2

Compose v2 إضافة تُستدعى كـ docker compose (بمسافة)، لا الثنائي القديم docker-compose بلغة Python. ثبّت الاثنين من مستودع Docker حتى تحصل على تصحيحات أمنية حالية.

bash
ssh root@YOUR_VPS_IP
apt update && apt -y install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update
apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

docker version
docker compose version

الخطوة 2: مستخدم النشر وتخطيط الدليل

تشغيل Compose كـ root يعمل، لكن مستخدمًا مخصصًا بعضوية في مجموعة docker أنظف. ضع المشروع في /opt أو /srv لا في /root حتى تكون النسخ الاحتياطية والأذونات واضحة. لا تجعل دليل .env قابلًا للكتابة للعالم أبدًا.

bash
adduser --disabled-password --gecos '' deploy
usermod -aG docker deploy
mkdir -p /srv/app
chown deploy:deploy /srv/app
chmod 750 /srv/app

# Log in as deploy for the rest of the file editing:
# su - deploy
# cd /srv/app

الخطوة 3: اكتب ملف Compose بعقلية إنتاج

الملف أدناه قالب. استبدل صورة التطبيق بصورتك. لا يُنشر Postgres إلى 0.0.0.0:5432 — يُنشر Caddy فقط على 80 و443. توقف فحوصات الصحة الوكيل العكسي عن إرسال حركة إلى تطبيق ما زال يهاجر قاعدة البيانات. ثبّت وسوم الصور؛ latest هو كيف يحدث الكسر المفاجئ.

bash
# /srv/app/compose.yaml
services:
  caddy:
    image: caddy:2.8-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      app:
        condition: service_healthy

  app:
    image: ghcr.io/example/webapp:1.4.2
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
      interval: 10s
      timeout: 3s
      retries: 10
    networks:
      - frontend
      - backend

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file: .env
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - backend

networks:
  frontend: {}
  backend: {}

volumes:
  pgdata:
  caddy_data:
  caddy_config:

الخطوة 4: الأسرار و.env وCaddyfile

ضع كلمات المرور في .env على الخادم، chmod 600، ولا تُودِع ذلك الملف أبدًا. ولّد كلمات المرور بـ openssl rand -base64 32. يحتاج Caddyfile فقط إلى وكيل عكسي لاسم خدمة التطبيق على شبكة Docker.

bash
# /srv/app/.env (permissions 600)
POSTGRES_USER=app
POSTGRES_PASSWORD=change-me-long-random
POSTGRES_DB=app
# plus any APP_SECRET / NEXTAUTH_SECRET your image needs

# /srv/app/Caddyfile
app.example.com {
    encode gzip
    reverse_proxy app:8000
}

chmod 600 /srv/app/.env
cd /srv/app
docker compose up -d
docker compose ps
docker compose logs -f --tail=100

الخطوة 5: جدار الحماية والسجلات وحدود الموارد

يجب أن يسمح UFW بـ 22 و80 و443 فقط. يتجاوز Docker أحيانًا UFW للمنافذ المنشورة؛ إن احتجت جدار حماية مضيف صارم فانظر تفاعل Docker + UFW الحالي لإصدار Ubuntu لديك أو انشر المنافذ على 127.0.0.1 فقط وضع Caddy مضيف أمامها. ضع حدود ذاكرة في Compose حتى لا يلتهم Postgres الـ VPS.

bash
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# Optional in compose.yaml under app or db:
#    deploy:
#      resources:
#        limits:
#          memory: 512M

# Logs (do not let json-file grow forever):
# { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
# in /etc/docker/daemon.json then: systemctl restart docker

الخطوة 6: التحديثات والتراجع

التحديث الممل الذي يعمل: اسحب صورًا جديدة، up -d، راقب فحوصات الصحة، أبقِ الوسم السابق في git حتى تستطيع التثبيت رجوعًا. لا تنفّذ docker compose down على قاعدة بيانات إنتاج ما لم تقصد إيقاف قبول الحركة. down -v يحذف الأحجام — هذا مسح لا إعادة تشغيل.

bash
cd /srv/app
git pull   # if the Compose file lives in git

# Change the image tag in compose.yaml, then:
docker compose pull
docker compose up -d
docker compose ps

# Rollback: set the old tag, pull, up -d again
# NEVER: docker compose down -v   # this deletes named volumes

انسخ الأحجام المسمّاة احتياطيًا

لقطة VPS جيدة. تفريغ Postgres منطقي أفضل إن احتجت الاستعادة على جهاز آخر. جدول هذا بـ cron. اختبر استعادة مرة، وإلا فليس لديك نسخ احتياطية — لديك ملفات تأمل أنها نسخ احتياطية.

bash
# Postgres dump while the db container is running:
docker compose exec -T db pg_dump -U app app | gzip > /var/backups/app-$(date +%F).sql.gz

# Copy off-box
# scp /var/backups/app-*.sql.gz backup-host:~

# Restore sketch (maintenance window):
# gunzip -c app-2026-08-19.sql.gz | docker compose exec -T db psql -U app app

استكشاف الأخطاء

اقرأ docker compose ps وdocker compose logs service. إخفاقات سحب الصور عادة حدود معدل ghcr/docker hub أو صورة خاصة بلا تسجيل دخول. 502 من Caddy يعني أن التطبيق غير سليم أو اسم مضيف الوكيل خاطئ (استخدم اسم خدمة Compose لا localhost من داخل حاوية Caddy). أخطاء الأذن على الأحجام غالبًا تعني أن الصورة تعمل كـ uid 1000 بينما دليل المضيف root.

  • compose: command not found — ثبّت الاسم القديم للثنائي؛ استخدم docker compose
  • port is already allocated — شيء آخر يملك 80/443 (Apache أو Nginx أو Caddy آخر)
  • database connection refused — بدأ التطبيق قبل جاهزية Postgres؛ استخدم healthcheck + شرط depends_on
  • disk full — docker system df ثم قلّم الصور غير المستخدمة بحذر
  • permission denied on docker.sock — المستخدم ليس في مجموعة docker أو تحتاج جلسة دخول جديدة

الأمان

لا تنشر منافذ قاعدة البيانات. لا تشغّل privileged: true. لا تركّب /var/run/docker.sock في حاوية تطبيق ما لم تكتب مدير حاويات عن قصد. أبقِ المحرك محدَّثًا. امسح صورك إن بنيتها. .env على القرص ما زال ملف سر — قيّد من يستطيع ssh إلى الصندوق.

  • لا 5432 / 3306 / 6379 عامة
  • ثبّت ملخصات الصور أو على الأقل وسومًا غير قابلة للتغيير
  • chmod 600 .env
  • Unattended-upgrades لنظام المضيف ما زالت مهمة
  • مشروع Compose واحد لكل تطبيق يُبقي نصف قطر الضرر أصغر

نصائح

  • ضع compose.yaml في git؛ أبقِ .env.example بلا أسرار حقيقية
  • استخدم ملفات تعريف للعمال الاختياريين (docker compose --profile workers up -d)
  • راقب docker stats عندما تحدّد حجم VPS
  • لربط مصدر الشفرة هذا ليس نمط الإنتاج — اخبز صورة
  • إن نمت الحزمة إلى عدة VPS فانظر التنسيق حينها — لا قبل ذلك

إعداد Compose شبه إنتاجي على VPS واحد هو: حزم Docker الرسمية، و.env مُقفل، وملف YAML لا ينشر قاعدة البيانات، وفحوصات صحة، ووكيل عكسي على 80/443، وUFW، ووسوم صور تستطيع التراجع إليها، وتفريغات Postgres استعدتها فعلًا مرة. ابدأ من القالب في هذه المقالة، استبدل صورة التطبيق، وعامل docker compose down -v كأمر مدمّر بالاحترام نفسه لـ rm -rf.