Cum să deplasezi aplicații cu Docker Compose pe un VPS
Un ghid practic de producție pentru Docker Compose v2 pe Ubuntu: instalează motorul, scrie un fișier Compose cu rețele și volume, gestionează secretele, pune un reverse proxy în față, actualizează cu lansări fără panică și fă backup la volumele denumite.

Docker Compose este modul obișnuit de a rula un teanc mic pe un VPS: o aplicație web, o bază de date, un cache Redis și un reverse proxy, descrise într-un fișier YAML. Nu e Kubernetes. Nu vei avea failover pe mai multe noduri. Vei avea o configurație repetabilă, verificabilă, pe care o poți recrea pe un VPS Hiddence nou în câteva minute — exact ce au nevoie majoritatea proiectelor laterale și produselor mici.
Acest articol presupune că vrei ceva mai aproape de producție decât docker run într-o vineri seara. Vom instala Docker Engine și pluginul Compose, vom crea un utilizator de deploy fără root, vom scrie un fișier Compose cu healthcheck-uri și politici de repornire, vom ține secretele în afara Git, vom publica pe internet doar porturile reverse proxy și vom defini o rutină de actualizare și backup. Teancul exemplu e o aplicație web tipică plus PostgreSQL plus Caddy, dar același model funcționează pentru Node, PHP, Python sau o grămadă de workeri.
De ce Compose pe un singur VPS încă are sens
Oamenii sar la Kubernetes pentru că e la modă, apoi petrec weekendul pe YAML pentru un site. Compose rămâne lizibil. Versionezi fișierul, documentezi variabilele de mediu și poți face diff unei modificări înainte să o aplici. Izolarea e suficientă: un container de aplicație compromis nu ar trebui să țină socket-ul UNIX al bazei dacă ai folosit rețele și utilizatori cu privilegii minime. Limitele de resurse opresc o scurgere de memorie să înghețe tot VPS-ul.
- Un fișier descrie tot teancul
- Volumele denumite supraviețuiesc recreării containerelor
- Rețelele Docker interne țin Postgres departe de internetul public
- restart: unless-stopped acoperă majoritatea repornirilor
- Ușor de copiat pe un al doilea VPS când crești din primul
Cerințe
Folosește un VPS cu suficient RAM pentru aplicație plus Postgres. Un plan de 2 GB e un minim realist pentru aplicație + bază + proxy. Se presupune Ubuntu 24.04. Nu instala Docker dintr-un Snap aleatoriu fără să citești ce face cu cgroups; pașii de mai jos folosesc depozitul apt oficial Docker.
- Ubuntu 22.04/24.04, root sau sudo
- 2 GB RAM recomandat (1 GB doar pentru teancuri minuscule fără Postgres)
- Un domeniu care pointează către VPS dacă vrei HTTPS automat
- Git sau alt mod de a copia proiectul pe server
Pasul 1: Instalează Docker Engine și Compose v2
Compose v2 e un plugin invocat ca docker compose (cu spațiu), nu vechiul binar Python docker-compose. Instalează ambele din depozitul Docker ca să primești patch-uri de securitate actuale.
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 versionPasul 2: Utilizator de deploy și aspectul directoarelor
Rularea Compose ca root funcționează, dar un utilizator dedicat cu membru în grupul docker e mai curat. Pune proiectul în /opt sau /srv, nu în /root, ca backup-urile și permisiunile să fie evidente. Nu face niciodată scriibil pentru toți directorul care conține .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/appPasul 3: Scrie un fișier Compose gândit pentru producție
Fișierul de mai jos e un șablon. Înlocuiește imaginea aplicației cu a ta. Postgres nu e publicat pe 0.0.0.0:5432 — doar Caddy e publicat pe 80 și 443. Healthcheck-urile opresc un reverse proxy să trimită trafic către o aplicație care încă migrează baza. Fixează etichetele de imagine; latest e modul în care apar stricăciuni prin surpriză.
# /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:Pasul 4: Secrete, .env și Caddyfile
Pune parolele în .env pe server, chmod 600, și nu face niciodată commit la acel fișier. Generează parole cu openssl rand -base64 32. Caddyfile trebuie doar să reverse-proxy către numele de serviciu al aplicației pe rețeaua 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=100Pasul 5: Firewall, jurnale și limite de resurse
UFW ar trebui să permită doar 22, 80 și 443. Docker ocolește uneori UFW pentru porturi publicate; dacă ai nevoie de un firewall strict pe gazdă, uită-te la interacțiunea curentă Docker + UFW pentru versiunea ta de Ubuntu sau publică porturi doar pe 127.0.0.1 și pune un Caddy pe gazdă în față. Setează limite de memorie în Compose ca Postgres să nu poată mânca VPS-ul.
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 dockerPasul 6: Actualizări și rollback
Actualizarea plictisitoare care funcționează: trage imagini noi, up -d, urmărește healthcheck-urile, păstrează eticheta anterioară în git ca să poți reveni. Nu rula docker compose down pe o bază de producție decât dacă intenționezi să oprești acceptarea traficului. down -v șterge volumele — e o ștergere, nu o repornire.
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 volumesFă backup la volumele denumite
Un snapshot VPS e bun. Un dump logic Postgres e mai bun dacă trebuie să restaurezi pe altă mașină. Programează asta cu cron. Testează o restaurare o dată, altfel nu ai backup-uri — ai fișiere despre care speri că sunt backup-uri.
# 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 appDepanare
Citește docker compose ps și docker compose logs service. Eșecurile de pull de imagine sunt de obicei limite de rată ghcr/docker hub sau o imagine privată fără autentificare. 502 de la Caddy înseamnă că aplicația nu e healthy sau numele de gazdă al proxy-ului e greșit (folosește numele de serviciu Compose, nu localhost, din interiorul containerului Caddy). Erorile de permisiuni pe volume înseamnă adesea că imaginea rulează ca uid 1000 în timp ce directorul gazdă e root.
- compose: command not found — ai instalat vechiul nume de binar; folosește docker compose
- port is already allocated — altceva deține 80/443 (Apache, Nginx, alt Caddy)
- database connection refused — aplicația a pornit înainte ca Postgres să fie gata; folosește healthcheck + depends_on condition
- disk full — docker system df, apoi prune imagini nefolosite cu grijă
- permission denied on docker.sock — utilizatorul nu e în grupul docker sau ai nevoie de o sesiune de autentificare nouă
Securitate
Nu publica porturi de bază de date. Nu rula privileged: true. Nu monta /var/run/docker.sock într-un container de aplicație decât dacă scrii deliberat un manager de containere. Ține motorul actualizat. Scanează propriile imagini dacă le construiești. .env pe disc e tot un fișier secret — restricționează cine poate face ssh la cutie.
- Fără 5432 / 3306 / 6379 public
- Fixează digest-uri de imagine sau cel puțin etichete imuabile
- chmod 600 .env
- Unattended-upgrades pentru OS-ul gazdei încă contează
- Un proiect Compose per aplicație ține raza de explozie mai mică
Sfaturi
- Pune compose.yaml în git; păstrează .env.example fără secrete reale
- Folosește profiluri pentru workeri opționali (docker compose --profile workers up -d)
- Urmărește docker stats când dimensionezi VPS-ul
- Pentru bind mounts de cod sursă, acesta nu e modelul de producție — coace o imagine
- Dacă teancul crește la mai multe VPS, atunci uită-te la orhestrare — nu înainte
O configurație Compose aproape de producție pe un VPS e: pachete Docker oficiale, un .env blocat, un fișier YAML care nu publică baza, healthcheck-uri, un reverse proxy pe 80/443, UFW, etichete de imagine pe care le poți da înapoi și dump-uri Postgres pe care le-ai restaurat cu adevărat o dată. Pornește de la șablonul din acest articol, înlocuiește imaginea aplicației și tratează docker compose down -v ca pe o comandă distructivă cu același respect ca rm -rf.