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

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 حتى تحصل على تصحيحات أمنية حالية.
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 قابلًا للكتابة للعالم أبدًا.
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 هو كيف يحدث الكسر المفاجئ.
# /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.
# /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.
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 يحذف الأحجام — هذا مسح لا إعادة تشغيل.
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. اختبر استعادة مرة، وإلا فليس لديك نسخ احتياطية — لديك ملفات تأمل أنها نسخ احتياطية.
# 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.