Како распоређивати апликације са Docker Compose на VPS
Практичан продукциони водич за Docker Compose v2 на Ubuntu: инсталација механизма, Compose датотека са мрежама и волуменима, тајне, реверзни прокси испред, ажурирања без панике и резервне копије именованих волумена.

Docker Compose је уобичајен начин да на једном VPS водите мали стек: веб апликација, база, Redis кеш и реверзни прокси у једној YAML датотеци. То није Kubernetes. Нећете добити failover на више чворова. Добићете поновљиво, прегледиво подешавање које за минуте подигнете на новом Hiddence VPS — управо оно што већини споредних пројеката и малих производа треба.
Овај чланак претпоставља да желите нешто ближе продукцији од docker run у петак увече. Инсталираћемо Docker Engine и Compose додатак, направити корисника за распоређивање без root у свакодневици, написати Compose датотеку са healthcheck-овима и политиком рестарта, тајне држати ван 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; кораци испод користе званични Docker apt репозиторијум.
- 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. 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:Корак 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, гледајте 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Правите резервне копије именованих волумена
Снимак 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 услуга. Неуспеси повлачења имиџа обично су лимити ghcr/Docker Hub или приватни имиџ без пријаве. 502 од Caddy значи да апликација није здрава или је hostname проксија погрешан (из 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 који не објављује базу, healthcheck-ови, реверзни прокси на 80/443, UFW, ознаке имиџа на које можете да се вратите и dump-ови Postgres-а које сте бар једном вратили. Почните од шаблона у овом чланку, замените имиџ апликације и docker compose down -v третирајте као деструктивну команду са истим поштовањем као rm -rf.