Hur du driftsätter applikationer med Docker Compose på en VPS
En praktisk produktionsguide till Docker Compose v2 på Ubuntu: installera motorn, skriv en Compose-fil med nätverk och volymer, hantera hemligheter, sätt en omvänd proxy framför, uppdatera med lugna utrullningar och säkerhetskopiera namngivna volymer.

Docker Compose är det vanliga sättet att köra en liten stack på en VPS: en webbapp, en databas, en Redis-cache och en omvänd proxy, beskrivna i en YAML-fil. Det är inte Kubernetes. Du får inte failover över flera noder. Du får en upprepningsbar, granskningsbar uppsättning som du kan återskapa på en ny Hiddence-VPS på minuter — vilket är precis vad de flesta sidoprojekt och små produkter behöver.
Den här artikeln utgår från att du vill ha något närmare produktion än docker run en fredagskväll. Vi installerar Docker Engine och Compose-pluginet, skapar en deploy-användare utan root, skriver en Compose-fil med healthchecks och omstartspolicyer, håller hemligheter borta från Git, publicerar bara de omvända proxy-portarna mot internet och definierar en uppdaterings- och säkerhetskopieringsrutin. Exempelstacken är en typisk webbapp plus PostgreSQL plus Caddy, men samma mönster fungerar för Node, PHP, Python eller en samling workers.
Varför Compose på en enda VPS fortfarande är vettigt
Folk hoppar till Kubernetes för att det är på modet och lägger sedan helgen på YAML för en webbplats. Compose förblir läsbart. Du versionshanterar filen, dokumenterar miljövariabler och kan diff:a en ändring innan du tillämpar den. Isoleringen räcker: en komprometterad appcontainer ska inte hålla databasens UNIX-socket om du använt nätverk och användare med minsta behörighet. Resursgränser stoppar en minnesläcka från att frysa hela VPS:en.
- En fil beskriver hela stacken
- Namngivna volymer överlever att containrar återskapas
- Interna Docker-nätverk håller Postgres borta från det publika internet
- restart: unless-stopped täcker de flesta omstarter
- Lätt att kopiera till en andra VPS när du växer ur den första
Krav
Använd en VPS med tillräckligt RAM för appen plus Postgres. En 2 GB-plan är ett realistiskt minimum för app + databas + proxy. Ubuntu 24.04 antas. Installera inte Docker från en slumpmässig Snap utan att läsa vad den gör med cgroups; stegen nedan använder Dockers officiella apt-arkiv.
- Ubuntu 22.04/24.04, root eller sudo
- 2 GB RAM rekommenderas (1 GB bara för små stackar utan Postgres)
- En domän som pekar på VPS:en om du vill ha automatisk HTTPS
- Git eller ett annat sätt att kopiera projektet till servern
Steg 1: Installera Docker Engine och Compose v2
Compose v2 är ett plugin som anropas som docker compose (med mellanslag), inte den gamla docker-compose Python-binären. Installera båda från Dockers arkiv så att du får aktuella säkerhetspatchar.
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 versionSteg 2: Deploy-användare och kataloglayout
Att köra Compose som root fungerar, men en dedikerad användare med medlemskap i docker-gruppen är renare. Lägg projektet i /opt eller /srv, inte i /root, så att säkerhetskopior och behörigheter är uppenbara. Gör aldrig katalogen som innehåller .env skrivbar för alla.
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/appSteg 3: Skriv en produktionsinriktad Compose-fil
Filen nedan är en mall. Byt app-imagen mot din. Postgres publiceras inte till 0.0.0.0:5432 — bara Caddy publiceras på 80 och 443. Healthchecks stoppar en omvänd proxy från att skicka trafik till en app som fortfarande migrerar databasen. Lås image-taggar; latest är hur överraskningsbrott uppstår.
# /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:Steg 4: Hemligheter, .env och Caddyfile
Lägg lösenord i .env på servern, chmod 600, och commita aldrig den filen. Generera lösenord med openssl rand -base64 32. Caddyfile behöver bara reverse-proxy:a till appens tjänstnamn på Docker-nätverket.
# /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=100Steg 5: Brandvägg, loggar och resursgränser
UFW ska bara tillåta 22, 80 och 443. Docker kringgår ibland UFW för publicerade portar; om du behöver en strikt värdbrandvägg, slå upp den aktuella Docker + UFW-interaktionen för din Ubuntu-version eller publicera portar bara på 127.0.0.1 och sätt en värd-Caddy framför. Sätt minnesgränser i Compose så att Postgres inte kan äta upp VPS:en.
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 dockerSteg 6: Uppdateringar och återställningar
Den tråkiga uppdateringen som fungerar: hämta nya images, up -d, bevaka healthchecks, behåll föregående tagg i git så att du kan låsa tillbaka. Kör inte docker compose down på en produktionsdatabas om du inte avser att sluta ta emot trafik. down -v tar bort volymer — det är en radering, inte en omstart.
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 volumesSäkerhetskopiera namngivna volymer
En VPS-ögonblicksbild är bra. En logisk Postgres-dump är bättre om du behöver återställa på en annan maskin. Schemalägg detta med cron. Testa en återställning en gång, annars har du inte säkerhetskopior — du har filer du hoppas är säkerhetskopior.
# 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 appFelsökning
Läs docker compose ps och docker compose logs service. Image-hämtningsfel är vanligtvis ghcr/docker hub-hastighetsgränser eller en privat image utan inloggning. 502 från Caddy betyder att appen inte är healthy eller att proxy-värdnamnet är fel (använd Compose-tjänstnamnet, inte localhost, inifrån Caddy-containern). Behörighetsfel på volymer betyder ofta att imagen körs som uid 1000 medan värdkatalogen är root.
- compose: command not found — du installerade det gamla binärnamnet; använd docker compose
- port is already allocated — något annat äger 80/443 (Apache, Nginx, en annan Caddy)
- database connection refused — appen startade innan Postgres var redo; använd healthcheck + depends_on condition
- disk full — docker system df, rensa sedan oanvända images försiktigt
- permission denied on docker.sock — användaren är inte i docker-gruppen, eller så behöver du en ny inloggningssession
Säkerhet
Publicera inte databaserportar. Kör inte privileged: true. Montera inte /var/run/docker.sock i en appcontainer om du inte medvetet skriver en containerhanterare. Håll motorn uppdaterad. Skanna dina egna images om du bygger dem. .env på disk är fortfarande en hemlig fil — begränsa vem som kan ssh:a till maskinen.
- Ingen publik 5432 / 3306 / 6379
- Lås image-digests eller åtminstone oföränderliga taggar
- chmod 600 .env
- Unattended-upgrades för värd-OS:et spelar fortfarande roll
- Ett Compose-projekt per app håller blast radius mindre
Tips
- Lägg compose.yaml i git; behåll .env.example utan riktiga hemligheter
- Använd profiler för valfria workers (docker compose --profile workers up -d)
- Bevaka docker stats när du dimensionerar VPS:en
- För bind mounts av källkod är det här inte produktionsmönstret — baka en image
- Om stacken växer till flera VPS, titta då på orkestrering — inte innan
En produktionslik Compose-uppsättning på en VPS är: officiella Docker-paket, en nedlåst .env, en YAML-fil som inte publicerar databasen, healthchecks, en omvänd proxy på 80/443, UFW, image-taggar du kan rulla tillbaka och Postgres-dumpar du faktiskt har återställt en gång. Börja från mallen i den här artikeln, byt app-imagen och behandla docker compose down -v som ett destruktivt kommando med samma respekt som rm -rf.