نحوه استقرار برنامهها با Docker Compose روی VPS
راهنمای عملی تولید برای Docker Compose v2 روی Ubuntu: موتور را نصب کنید، فایل Compose با شبکه و volume بنویسید، رازها را مدیریت کنید، پروکسی معکوس جلو بگذارید، با بهروزرسانی بدون وحشت پیش بروید و volumeهای نامدار را پشتیبان بگیرید.

Docker Compose راه معمول اجرای پشتهٔ کوچک روی یک VPS است: برنامهٔ وب، پایگاه داده، کش Redis و پروکسی معکوس، همه در یک فایل YAML. Kubernetes نیست. failover چندگرهی نمیگیرید. راهاندازی تکرارپذیر و قابل بازبینی میگیرید که روی VPS تازهٔ Hiddence در چند دقیقه دوباره ساخته میشود — دقیقاً همان چیزی که بیشتر پروژههای جانبی و محصولات کوچک لازم دارند.
این مقاله فرض میکند چیزی نزدیکتر به تولید از docker run جمعه شب میخواهید. Docker Engine و افزونهٔ Compose را نصب میکنیم، کاربر استقرار غیر root میسازیم، فایل Compose با healthcheck و سیاست ریاستارت مینویسیم، رازها را از Git دور نگه میداریم، فقط پورتهای پروکسی معکوس را به اینترنت منتشر میکنیم و روال بهروزرسانی و پشتیبان تعریف میکنیم. پشتهٔ نمونه برنامهٔ وب معمولی بهعلاوهٔ PostgreSQL بهعلاوهٔ Caddy است، اما همان الگو برای Node، PHP، Python یا گروهی worker هم کار میکند.
چرا Compose روی یک VPS هنوز معنا دارد
آدمها چون مد است به Kubernetes میپرند، بعد آخر هفته را برای یک وبسایت روی YAML میگذرانند. Compose خوانا میماند. فایل را نسخهبندی میکنید، متغیرهای محیط را مستند میکنید و قبل از اعمال میتوانید diff بگیرید. ایزولاسیون کافی است: اگر شبکه و کاربر با کمترین امتیاز استفاده کردید کانتینر برنامهٔ نفوذشده نباید سوکت UNIX پایگاه داده را در دست داشته باشد. محدودیت منابع جلوی یخ زدن کل VPS با یک نشت حافظه را میگیرد.
- یک فایل کل پشته را توصیف میکند
- volumeهای نامدار از بازسازی کانتینر جان سالم بهدر میبرند
- شبکههای داخلی Docker، Postgres را از اینترنت عمومی دور نگه میدارند
- restart: unless-stopped بیشتر ریبوتها را پوشش میدهد
- وقتی از اولی بزرگتر شدید کپی به VPS دوم آسان است
پیشنیازها
VPS با RAM کافی برای برنامه بهعلاوهٔ Postgres استفاده کنید. طرح 2 گیگابایت حداقل واقعبینانه برای برنامه + پایگاه + پروکسی است. Ubuntu 24.04 فرض شده. Docker را از Snap تصادفی بدون خواندن اثرش روی cgroups نصب نکنید؛ گامهای زیر از مخزن رسمی apt مربوط به Docker استفاده میکنند.
- Ubuntu 22.04/24.04، root یا sudo
- 2 گیگابایت RAM توصیه میشود (1 گیگابایت فقط برای پشتهٔ خیلی کوچک بدون Postgres)
- اگر HTTPS خودکار میخواهید دامنهای که به VPS اشاره کند
- Git یا راه دیگری برای کپی پروژه روی سرور
گام ۱: Docker Engine و Compose v2 را نصب کنید
Compose v2 افزونهای است که بهصورت docker compose (با فاصله) صدا زده میشود، نه باینری قدیمی Python بهنام docker-compose. هر دو را از مخزن 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گام ۲: کاربر استقرار و چیدمان پوشه
اجرای 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گام ۳: فایل Compose با ذهنیت تولید بنویسید
فایل زیر قالب است. ایمیج برنامه را با مال خودتان عوض کنید. Postgres به 0.0.0.0:5432 منتشر نمیشود — فقط Caddy روی 80 و 443 منتشر است. healthcheck جلوی فرستادن ترافیک پروکسی معکوس به برنامهای که هنوز پایگاه را مهاجرت میدهد میگیرد. تگ ایمیج را پین کنید؛ 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:گام ۴: رازها، .env و Caddyfile
رمزها را در .env روی سرور بگذارید، chmod 600 کنید و هرگز آن فایل را commit نکنید. رمز را با 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گام ۵: فایروال، لاگ و محدودیت منابع
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گام ۶: بهروزرسانی و بازگشت
بهروزرسانی کسلکنندهای که کار میکند: ایمیج جدید بکشید، up -d، healthcheck را ببینید، تگ قبلی را در 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از volumeهای نامدار پشتیبان بگیرید
اسنپشات VPS خوب است. اگر باید روی ماشین دیگر برگردانید dump منطقی 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 یعنی برنامه سالم نیست یا نام میزبان پروکسی غلط است (از داخل کانتینر Caddy نام سرویس Compose را استفاده کنید نه localhost). خطای مجوز روی volume اغلب یعنی ایمیج با 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 بدون راز واقعی نگه دارید
- برای worker اختیاری از profile استفاده کنید (docker compose --profile workers up -d)
- وقتی اندازهٔ VPS را میسنجید docker stats را ببینید
- bind mount کد منبع الگوی تولید نیست — ایمیج بپزید
- اگر پشته به چند VPS رسید آنوقت به ارکستراسیون نگاه کنید — نه قبلش
راهاندازی شبهتولیدی Compose روی یک VPS این است: بستههای رسمی Docker، .env قفلشده، فایل YAML که پایگاه را منتشر نمیکند، healthcheck، پروکسی معکوس روی 80/443، UFW، تگ ایمیجی که بتوانید برگردانید، و dumpهای Postgres که یکبار واقعاً بازیابی کردهاید. از قالب این مقاله شروع کنید، ایمیج برنامه را عوض کنید و با docker compose down -v مثل rm -rf با احترام مخرب برخورد کنید.