Powrót do bloga
Sierpień 19, 2026Poradniki

Jak wdrażać aplikacje Docker Compose na VPS

Praktyczny, produkcyjny poradnik Docker Compose v2 na Ubuntu: instalacja silnika, plik Compose z sieciami i wolumenami, sekrety, reverse proxy z przodu, aktualizacje bez paniki i kopie zapasowe nazwanych wolumenów.

Jak wdrażać aplikacje Docker Compose na VPS

Docker Compose to zwykły sposób na mały stos na jednym VPS: aplikacja WWW, baza, cache Redis i reverse proxy w jednym pliku YAML. To nie Kubernetes. Nie dostaniesz failoveru na wiele węzłów. Dostaniesz powtarzalną, recenzowalną konfigurację, którą odtworzysz na nowym VPS Hiddence w minuty — a właśnie tego potrzebuje większość projektów pobocznych i małych produktów.

Ten artykuł zakłada, że chcesz coś bliższego produkcji niż docker run w piątkowy wieczór. Zainstalujemy Docker Engine i wtyczkę Compose, utworzymy użytkownika wdrożeniowego bez root na co dzień, napiszemy plik Compose z healthcheckami i polityką restartu, trzymamy sekrety poza Git, wystawimy do internetu tylko porty reverse proxy i opiszemy rutynę aktualizacji oraz kopii. Przykładowy stos to typowa aplikacja WWW plus PostgreSQL plus Caddy, ale ten sam wzorzec działa dla Node, PHP, Python albo zestawu workerów.

Dlaczego Compose na jednym VPS nadal ma sens

Ludzie skaczą do Kubernetesa, bo tak wypada, a potem spędzają weekend na YAML dla jednej witryny. Compose zostaje czytelny. Wersjonujesz plik, dokumentujesz zmienne środowiskowe i możesz zrobić diff zmiany przed zastosowaniem. Izolacja wystarczy: skompromitowany kontener aplikacji nie powinien trzymać gniazda UNIX bazy, jeśli użyłeś sieci i użytkowników z minimalnymi uprawnieniami. Limity zasobów zatrzymują jeden wyciek pamięci przed zamrożeniem całego VPS.

  • Jeden plik opisuje cały stos
  • Nazwane wolumeny przeżywają odtworzenie kontenerów
  • Wewnętrzne sieci Docker trzymają Postgres z dala od publicznego internetu
  • restart: unless-stopped pokrywa większość rebootów
  • Łatwo skopiować na drugi VPS, gdy pierwszy przestaje wystarczać

Wymagania

Użyj VPS z wystarczającą ilością RAM na aplikację plus Postgres. Plan 2 GB to realistyczne minimum dla aplikacji + bazy + proxy. Zakładamy Ubuntu 24.04. Nie instaluj Dockera z przypadkowego Snapa bez czytania, co robi z cgroups; kroki poniżej używają oficjalnego repozytorium apt Dockera.

  • Ubuntu 22.04/24.04, root lub sudo
  • Zalecane 2 GB RAM (1 GB tylko dla malutkich stosów bez Postgres)
  • Domena wskazująca na VPS, jeśli chcesz automatyczny HTTPS
  • Git albo inny sposób skopiowania projektu na serwer

Krok 1. Zainstaluj Docker Engine i Compose v2

Compose v2 to wtyczka wywoływana jako docker compose (ze spacją), nie stary binarny docker-compose w Pythonie. Instaluj oba z repozytorium Dockera, żeby dostawać bieżące łatki bezpieczeństwa.

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żytkownik wdrożeniowy i układ katalogów

Compose jako root działa, ale dedykowany użytkownik w grupie docker jest czyściejszy. Projekt kładź w /opt albo /srv, nie w /root, żeby kopie i uprawnienia były oczywiste. Nigdy nie dawaj światu zapisu do katalogu z .env.

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. Napisz plik Compose z myślą o produkcji

Plik poniżej to szablon. Zamień obraz aplikacji na swój. Postgres nie jest publikowany na 0.0.0.0:5432 — na zewnątrz tylko Caddy na 80 i 443. Healthchecki nie pozwalają reverse proxy słać ruchu do aplikacji, która jeszcze migruje bazę. Przypinaj tagi obrazów; latest to przepis na niespodziewane awarie.

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. Sekrety, .env i Caddyfile

Hasła trzymaj w .env na serwerze, chmod 600, i nigdy nie commituj tego pliku. Hasła generuj openssl rand -base64 32. Caddyfile musi tylko zrobić reverse proxy na nazwę usługi aplikacji w sieci 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, logi i limity zasobów

UFW powinien zezwalać tylko na 22, 80 i 443. Docker czasem omija UFW dla opublikowanych portów; jeśli potrzebujesz twardego firewalla na hoście, sprawdź aktualną interakcję Docker + UFW dla Twojej Ubuntu albo publikuj porty tylko na 127.0.0.1 i stawiaj Caddy na hoście. Ustaw limity pamięci w Compose, żeby Postgres nie zjadł 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. Aktualizacje i rollbacki

Nudna aktualizacja, która działa: ściągnij nowe obrazy, up -d, obserwuj healthchecki, trzymaj poprzedni tag w git, żeby przypiąć wstecz. Nie rób docker compose down na produkcyjnej bazie, chyba że chcesz przestać przyjmować ruch. down -v kasuje wolumeny — to wycieranie, nie 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

Kopie zapasowe nazwanych wolumenów

Migawka VPS jest dobra. Logiczny dump Postgresa jest lepszy, jeśli odtwarzasz na inną maszynę. Zaplanuj to cronem. Przetestuj odtworzenie raz, albo nie masz kopii — masz pliki, co do których masz nadzieję, że są kopiami.

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

Rozwiązywanie problemów

Czytaj docker compose ps i docker compose logs usługa. Niepowodzenia pull obrazu to zwykle limity ghcr/Docker Hub albo prywatny obraz bez logowania. 502 z Caddy znaczy, że aplikacja nie jest healthy albo hostname proxy jest zły (użyj nazwy usługi Compose, nie localhost, z wnętrza kontenera Caddy). Błędy uprawnień na wolumenach często znaczą, że obraz działa jako uid 1000, a katalog na hoście należy do root.

  • compose: command not found — zainstalowałeś starą nazwę binarki; użyj docker compose
  • port is already allocated — coś innego trzyma 80/443 (Apache, Nginx, inny Caddy)
  • database connection refused — aplikacja wystartowała przed Postgresem; healthcheck + depends_on z condition
  • dysk pełny — docker system df, potem ostrożny prune nieużywanych obrazów
  • permission denied na docker.sock — użytkownik nie w grupie docker albo potrzebujesz nowej sesji logowania

Bezpieczeństwo

Nie publikuj portów bazy. Nie włączaj privileged: true. Nie montuj /var/run/docker.sock do kontenera aplikacji, chyba że celowo piszesz menedżer kontenerów. Aktualizuj silnik. Skanuj własne obrazy, jeśli je budujesz. .env na dysku nadal jest plikiem sekretu — ogranicz, kto może ssh na skrzynkę.

  • Brak publicznych 5432 / 3306 / 6379
  • Przypinaj digesty obrazów albo przynajmniej niemutowalne tagi
  • chmod 600 .env
  • Unattended-upgrades systemu hosta nadal mają znaczenie
  • Jeden projekt Compose na aplikację zmniejsza promień rażenia

Wskazówki

  • compose.yaml w git; .env.example bez prawdziwych sekretów
  • Profile dla opcjonalnych workerów (docker compose --profile workers up -d)
  • Obserwuj docker stats, gdy dobierasz rozmiar VPS
  • Bind-mount kodu źródłowego to nie wzorzec produkcyjny — zapiecz obraz
  • Gdy stos urośnie do kilku VPS, wtedy patrz na orkiestrację — nie wcześniej

Prawie produkcyjny Compose na jednym VPS to: oficjalne pakiety Dockera, zamknięty .env, YAML, który nie publikuje bazy, healthchecki, reverse proxy na 80/443, UFW, tagi obrazów, do których możesz wrócić, i dumpy Postgresa, które choć raz odtworzyłeś. Zacznij od szablonu z tego artykułu, podmień obraz aplikacji i traktuj docker compose down -v jak polecenie niszczące z takim samym szacunkiem jak rm -rf.