Wolkra

Handleidingen

Een app deployen met Docker Compose op een VPS

Deploy een app met Docker Compose op een VPS: installeer Docker op Ubuntu of Debian, schrijf compose.yaml, voeg HTTPS toe met Caddy, bewaar secrets veilig en werk bij.

Bijgewerkt 5 min. leestijd

Om met Docker Compose op een VPS te deployen, installeer je Docker Engine en de Compose-plugin, zet je een compose.yaml en een .env-bestand in een projectmap, voer je docker compose up -d uit en zet je een reverse proxy zoals Caddy ervoor voor HTTPS. Bijwerken is daarna docker compose pull && docker compose up -d.

Deze handleiding bouwt een kleine maar realistische stack: een webapp, een Postgres-database en Caddy voor automatische HTTPS, op Ubuntu 24.04 of Debian 12.

1. De server voorbereiden

Begin met een server waarop de eerste stappen zijn gezet: updates, een sudo-gebruiker, SSH-sleutels en een firewall. Voor deze stack is 2 GB geheugen een verstandig minimum.

Laat vóór je begint een DNS-record naar de server wijzen, zodat HTTPS de eerste keer meteen werkt:

text
app.example.com.   A      203.0.113.10        ; example address
app.example.com.   AAAA   2001:db8::10        ; example address

2. Docker Engine en Compose installeren

Gebruik de eigen repository van Docker; de pakketten van de distributie zijn vaak ouder.

bash
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc
sudo 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/$ID $VERSION_CODENAME stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

$ID is ubuntu of debian, dus dezelfde regels werken op beide. Controleer:

bash
sudo docker run --rm hello-world
docker compose version

Laat je gebruiker Docker zonder sudo draaien (log daarna uit en weer in):

bash
sudo usermod -aG docker $USER

Lid zijn van de groep docker staat in de praktijk gelijk aan root-toegang. Voeg alleen gebruikers toe die je vertrouwt.

Sneller: de Docker-app met één klik op Wolkra installeert Docker CE met Compose voor je.

3. Het project indelen

bash
mkdir -p ~/apps/myapp && cd ~/apps/myapp

Een duidelijke indeling maakt de server maanden later nog makkelijk te begrijpen:

text
~/apps/myapp/
├── compose.yaml
├── .env            # secrets, not in git
└── Caddyfile

4. compose.yaml schrijven

yaml
services:
  app:
    image: ghcr.io/example/myapp:1.4.2
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_URL: postgres://myapp:${POSTGRES_PASSWORD}@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    expose:
      - "3000"

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: myapp
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 10s
      retries: 5

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config

volumes:
  db-data:
  caddy-data:
  caddy-config:

Wat het vermelden waard is:

  • Pin imageversies (1.4.2, 16), zodat een herstart nooit onverwacht een nieuwe hoofdversie binnenhaalt.
  • expose, niet ports, voor de app en de database. Die zijn alleen bereikbaar binnen het Compose-netwerk. Alleen Caddy publiceert poorten.
  • Named volumes bewaren data als containers opnieuw worden aangemaakt.
  • depends_on met een health check wacht tot Postgres verbindingen accepteert.

Vervang ghcr.io/example/myapp door je eigen image en poort.

5. Secrets opslaan in .env

bash
cat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 24)
APP_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 .env

Compose leest .env uit de projectmap voor de vervanging van ${...}, en env_file geeft de waarden door aan de app. Voeg .env toe aan .gitignore als de map een git-checkout is.

6. HTTPS toevoegen met Caddy

Maak Caddyfile aan:

text
app.example.com {
    encode zstd gzip
    reverse_proxy app:3000
}

Caddy haalt automatisch een certificaat op en vernieuwt het, zodra DNS naar de server wijst en poorten 80 en 443 open zijn. Voor meer opties, zie de handleiding voor Caddy als reverse proxy.

7. De stack starten

bash
docker compose up -d
docker compose ps
docker compose logs -f caddy

Zodra Caddy certificate obtained successfully logt, open je https://app.example.com.

8. Let op de firewall

Docker voegt iptables-regels toe die vóór UFW worden uitgevoerd. Een poort die met ports: is gepubliceerd, is bereikbaar vanaf internet, ook als UFW zegt dat hij geblokkeerd is. Drie manieren om veilig te blijven:

  1. Publiceer alleen de reverse proxy (zoals hierboven).
  2. Bind al het andere aan localhost: "127.0.0.1:5432:5432".
  3. Filter vóór de server met een cloudfirewall, die Docker niet kan omzeilen. Sta 80 en 443 vanaf overal toe en 22 alleen vanaf je eigen adres.

9. Zonder drama bijwerken

Wijzig de imagetag in compose.yaml en voer daarna uit:

bash
docker compose pull
docker compose up -d
docker image prune -f

Compose maakt alleen de services opnieuw aan waarvan de image of configuratie is veranderd. Terugdraaien doe je door de oude tag terug te zetten en opnieuw up -d uit te voeren. Maak een snapshot vóór grote upgrades, zoals een nieuwe hoofdversie van Postgres, die een dump en restore vereist.

10. Back-ups van de data maken

Volumes staan onder /var/lib/docker/volumes. Voor een database is een logische dump veiliger dan bestanden kopiëren:

bash
docker compose exec -T db pg_dump -U myapp myapp | gzip > ~/backups/myapp-$(date +%F).sql.gz

Laat dat elke nacht via cron draaien en kopieer de bestanden van de server af. Back-ups op serverniveau helpen ook: bij Wolkra kosten dagelijkse back-ups 20% van de serverprijs en bewaren ze 7 herstelpunten. De handleiding voor een back-upstrategie brengt alles samen.

11. Logs en limieten

Standaard groeien Docker-logs onbeperkt. Begrens ze globaal in /etc/docker/daemon.json:

json
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

Voer daarna sudo systemctl restart docker uit. Je kunt ook per service het geheugen begrenzen met mem_limit: 512m, zodat één container de andere niet uithongert.

12. Starten bij het opstarten

restart: unless-stopped plus de ingeschakelde Docker-service zorgt ervoor dat de stack na een reboot terugkomt. Test het één keer:

bash
sudo reboot
# log in again
docker compose -f ~/apps/myapp/compose.yaml ps

13. Deployen vanuit CI

Draait de stack eenmaal, dan kunnen deploys automatisch. Een CI-job bouwt en pusht een image met een nieuwe tag, maakt daarna via SSH verbinding met de server en voert uit:

bash
cd ~/apps/myapp
sed -i 's|myapp:.*|myapp:1.4.3|' compose.yaml
docker compose pull app
docker compose up -d app

Geef de CI-job een eigen SSH-sleutel, beperkt tot deze server, zoals beschreven in de handleiding over SSH-sleutels.

Wanneer je verder groeit dan één server

Compose op één server brengt je ver. Heb je meer nodig, dan zijn dit de gebruikelijke volgende stappen:

Wil je veel apps beheren via een webinterface, dan bouwt de Coolify-app met één klik op hetzelfde Docker-fundament. Meer ideeën vind je bij Docker-hosting.

Veelgestelde vragen

Is Docker Compose goed genoeg voor productie?

Voor één server wel. Veel kleine en middelgrote apps draaien jarenlang op Compose. Stap pas over op een orchestrator als je meerdere hosts, automatische failover of rolling updates over machines heen nodig hebt.

Hoe groot moet mijn VPS zijn voor Docker Compose?

Tel op wat elke container gebruikt. Een webapp met Postgres en Redis zit ruim met 2 tot 4 GB geheugen; houd ruimte over voor het bouwen van images als je op de server bouwt.

Waarom blokkeert UFW mijn containerpoorten niet?

Docker voegt eigen iptables-regels toe die vóór UFW worden uitgevoerd. Publiceer poorten op 127.0.0.1 en zet er een reverse proxy voor, of filter met een cloudfirewall.

Moet ik images op de server bouwen?

Voor kleine projecten werkt dat. Voor herhaalbare deploys bouw je in CI, push je naar een registry en doe je op de server alleen een pull.

Kan ik het zelf installeren van Docker overslaan?

Ja. Kies bij Wolkra de app Docker CE met één klik wanneer je een server aanmaakt; die komt met Docker en de Compose-plugin al geïnstalleerd.

Start een server in Frankfurt

Maak een account aan, waardeer op en draai een server vanaf €3.49 per maand. Je betaalt alleen voor de uren die je gebruikt. Onbemeterde bandbreedte bij elk pakket.