Как да разгръщате приложения с 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 остава четим. Версионирате файла, документирате променливите на средата и можете да видите diff на промяна преди да я приложите. Изолацията стига: компрометиран контейнер на приложението не трябва да държи UNIX сокета на базата, ако сте ползвали мрежи и потребители с минимални права. Лимитите на ресурси спират един изтичане на памет да замрази целия VPS.
- Един файл описва целия стек
- Именуваните томове оцеляват пресъздаването на контейнери
- Вътрешните мрежи на Docker държат Postgres извън публичния интернет
- restart: unless-stopped покрива повечето рестарти
- Лесно се копира на втори VPS, когато първият стане тесен
Изисквания
Ползвайте VPS с достатъчно RAM за приложението плюс Postgres. План 2 GB е реалистичен минимум за приложение + база + прокси. Предполага се Ubuntu 24.04. Не инсталирайте Docker от случаен Snap, без да четете какво прави с cgroups; стъпките по-долу ползват официалното apt хранилище на Docker.
- Ubuntu 22.04/24.04, root или sudo
- Препоръчват се 2 GB RAM (1 GB само за мънички стекове без Postgres)
- Домейн към VPS, ако искате автоматичен HTTPS
- Git или друг начин да копирате проекта на сървъра
Стъпка 1. Инсталирайте 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Стъпка 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 трябва само да reverse-proxy към името на услугата на приложението в мрежата 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 услуга. Провалите при дърпане на образ обикновено са лимити на ghcr/Docker Hub или частен образ без вход. 502 от Caddy значи, че приложението не е здраво или името на хоста на проксито е грешно (от контейнера Caddy ползвайте името на услугата Compose, не localhost). Грешки за права на томове често значат, че образът върви като 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 с condition
- дискът е пълен — docker system df, после внимателен prune на неизползвани образи
- permission denied на docker.sock — потребителят не е в групата docker или ви трябва нова сесия за вход
Сигурност
Не публикувайте портове на базата. Не включвайте privileged: true. Не монтирайте /var/run/docker.sock в контейнер на приложение, освен ако нарочно пишете мениджър на контейнери. Дръжте двигателя обновен. Сканирайте собствените образи, ако ги строите. .env на диска все още е секретен файл — ограничете кой може да ssh към кутията.
- Няма публични 5432 / 3306 / 6379
- Фиксирайте digest на образи или поне неизменяеми тагове
- chmod 600 .env
- Unattended-upgrades на хостовата ОС все още имат значение
- Един проект Compose на приложение намалява радиуса на поражение
Съвети
- compose.yaml в git; .env.example без истински тайни
- Профили за незадължителни работници (docker compose --profile workers up -d)
- Гледайте docker stats, когато оразмерявате VPS
- Bind mount на изходен код не е продуктовият шаблон — изпечете образ
- Когато стекът нарасне до няколко VPS, тогава гледайте оркестрация — не преди това
Почти продуктов Compose на един VPS е: официални пакети Docker, заключен .env, YAML, който не публикува базата, проверки за здраве, обратен прокси на 80/443, UFW, тагове на образи, към които можете да се върнете, и дъмпове на Postgres, които поне веднъж сте възстановявали. Започнете от шаблона в тази статия, сменете образа на приложението и се отнасяйте към docker compose down -v като към разрушителна команда със същото уважение като към rm -rf.