Jak nasazovat aplikace pomocí Docker Compose na VPS
Praktický produkční návod k Docker Compose v2 na Ubuntu: instalace enginu, soubor Compose se sítěmi a svazky, tajemství, reverse proxy vpředu, aktualizace bez paniky a zálohy pojmenovaných svazků.

Docker Compose je obvyklý způsob, jak na jednom VPS provozovat malý zásobník: webová aplikace, databáze, cache Redis a reverse proxy v jednom YAML. Není to Kubernetes. Nedostanete failover na více uzlů. Dostanete opakovatelné, recenzovatelné nastavení, které na novém VPS Hiddence znovu postavíte během minut — a přesně to většina vedlejších projektů a malých produktů potřebuje.
Tento článek předpokládá, že chcete něco bližšího produkci než docker run v pátek večer. Nainstalujeme Docker Engine a plugin Compose, vytvoříme nenasazovacího uživatele bez root na denní bázi, napíšeme soubor Compose s healthchecky a politikou restartu, tajemství udržíme mimo Git, do internetu zveřejníme jen porty reverse proxy a definujeme rutinu aktualizace a zálohy. Příkladový zásobník je typická webová aplikace plus PostgreSQL plus Caddy, ale stejný vzor funguje pro Node, PHP, Python nebo sadu workerů.
Proč Compose na jednom VPS pořád dává smysl
Lidé skáčou ke Kubernetes, protože je to módní, a pak stráví víkend YAML kvůli jednomu webu. Compose zůstává čitelný. Verzujete soubor, dokumentujete proměnné prostředí a změnu můžete diffnout, než ji použijete. Izolace stačí: kompromitovaný kontejner aplikace by neměl držet UNIX socket databáze, pokud jste použili sítě a uživatele s minimálními právy. Limity prostředků zastaví jeden únik paměti před zamrznutím celého VPS.
- Jeden soubor popisuje celý zásobník
- Pojmenované svazky přežijí znovuvytvoření kontejnerů
- Interní sítě Docker drží Postgres mimo veřejný internet
- restart: unless-stopped pokrývá většinu rebootů
- Snadno zkopírujete na druhý VPS, až první přeroste
Požadavky
Použijte VPS s dostatkem RAM pro aplikaci plus Postgres. Tarif 2 GB je realistické minimum pro aplikaci + databázi + proxy. Předpokládá se Ubuntu 24.04. Neinstalujte Docker z náhodného Snapu, aniž byste četli, co dělá s cgroups; kroky níže používají oficiální apt repozitář Dockeru.
- Ubuntu 22.04/24.04, root nebo sudo
- Doporučeno 2 GB RAM (1 GB jen pro drobné zásobníky bez Postgres)
- Doména mířící na VPS, pokud chcete automatický HTTPS
- Git nebo jiný způsob, jak projekt dostat na server
Krok 1. Nainstalujte Docker Engine a Compose v2
Compose v2 je plugin volaný jako docker compose (s mezerou), ne starý pythonový binární docker-compose. Instalujte obojí z repozitáře Dockeru, abyste dostávali aktuální bezpečnostní záplaty.
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 versionKrok 2. Uživatel nasazení a rozložení adresářů
Compose jako root funguje, ale vyhrazený uživatel ve skupině docker je čistší. Projekt dávejte do /opt nebo /srv, ne do /root, ať jsou zálohy a práva zřejmá. Adresář s .env nikdy nesmí být zapisovatelný pro všechny.
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/appKrok 3. Napište soubor Compose s produkční myslí
Soubor níže je šablona. Obraz aplikace nahraďte svým. Postgres není publikovaný na 0.0.0.0:5432 — ven jde jen Caddy na 80 a 443. Healthchecky zabrání reverse proxy posílat provoz do aplikace, která ještě migruje databázi. Připněte tagy obrazů; latest je způsob, jak přijde překvapivé rozbití.
# /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:Krok 4. Tajemství, .env a Caddyfile
Hesla dejte do .env na serveru, chmod 600, a ten soubor nikdy necommitujte. Hesla generujte openssl rand -base64 32. Caddyfile stačí reverse-proxy na název služby aplikace v síti 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=100Krok 5. Firewall, logy a limity prostředků
UFW by měl povolit jen 22, 80 a 443. Docker někdy UFW u publikovaných portů obchází; pokud potřebujete přísný firewall na hostu, podívejte se na aktuální interakci Docker + UFW pro vaši Ubuntu, nebo publikujte porty jen na 127.0.0.1 a dejte dopředu Caddy na hostu. V Compose nastavte limity paměti, aby Postgres nesežral 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 dockerKrok 6. Aktualizace a rollbacky
Nudná aktualizace, která funguje: stáhněte nové obrazy, up -d, sledujte healthchecky, předchozí tag držte v gitu, abyste mohli pinovat zpět. Nedělejte docker compose down na produkční databázi, pokud nechcete přestat přijímat provoz. down -v maže svazky — to je smazání, ne restart.
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 volumesZálohujte pojmenované svazky
Snímek VPS je dobrý. Logický dump Postgresu je lepší, pokud obnovujete na jiný stroj. Naplánujte to cronem. Obnovu jednou otestujte, jinak nemáte zálohy — máte soubory, o kterých doufáte, že jsou zálohy.
# 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Řešení problémů
Čtěte docker compose ps a docker compose logs služba. Selhání stahování obrazu jsou obvykle limity ghcr/Docker Hub nebo soukromý obraz bez přihlášení. 502 od Caddy znamená, že aplikace není healthy nebo je hostname proxy špatně (z kontejneru Caddy použijte název služby Compose, ne localhost). Chyby práv na svazcích často znamenají, že obraz běží jako uid 1000, zatímco adresář na hostu patří root.
- compose: command not found — nainstalovali jste starý název binárky; použijte docker compose
- port is already allocated — něco jiného drží 80/443 (Apache, Nginx, jiný Caddy)
- database connection refused — aplikace nastartovala dřív než Postgres; healthcheck + depends_on s condition
- disk plný — docker system df, pak opatrný prune nepoužívaných obrazů
- permission denied na docker.sock — uživatel není ve skupině docker, nebo potřebujete novou přihlašovací relaci
Bezpečnost
Nepublikujte porty databáze. Nezapínejte privileged: true. Nemountujte /var/run/docker.sock do kontejneru aplikace, pokud záměrně nepíšete správce kontejnerů. Engine aktualizujte. Skenujte vlastní obrazy, pokud je stavíte. .env na disku je pořád tajný soubor — omezte, kdo může na box ssh.
- Žádné veřejné 5432 / 3306 / 6379
- Pinnujte digest obrazů nebo alespoň neměnné tagy
- chmod 600 .env
- Unattended-upgrades hostitelského OS pořád záleží
- Jeden projekt Compose na aplikaci zmenšuje poloměr výbuchu
Tipy
- compose.yaml v gitu; .env.example bez skutečných tajemství
- Profily pro volitelné workery (docker compose --profile workers up -d)
- Sledujte docker stats, když dimenzujete VPS
- Bind mount zdrojového kódu není produkční vzor — upečte obraz
- Až zásobník vyroste na několik VPS, teprve pak hledejte orchestraci — ne dřív
Produkčnější Compose na jednom VPS je: oficiální balíčky Dockeru, uzamčený .env, YAML, který nepublikuje databázi, healthchecky, reverse proxy na 80/443, UFW, tagy obrazů, ke kterým se umíte vrátit, a dumpy Postgresu, které jste aspoň jednou obnovili. Začněte šablonou z tohoto článku, nahraďte obraz aplikace a k docker compose down -v se chovejte jako k destruktivnímu příkazu se stejným respektem jako k rm -rf.