Zpět na blog
Srpen 19, 2026Návody

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ů.

Jak nasazovat aplikace pomocí Docker Compose na VPS

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.

bash
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

Krok 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.

bash
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

Krok 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í.

bash
# /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.

bash
# /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

Krok 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.

bash
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

Krok 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.

bash
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

Zá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.

bash
# 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.