← Terug naar tutorials

Meerdere Docker-services reverse proxyen met Traefik en automatische HTTPS

traefikdockerreverse proxyhttpslet's encrypt

Meerdere Docker-services reverse proxyen met Traefik en automatische HTTPS

Deze tutorial laat je stap voor stap zien hoe je meerdere Docker-services (bijv. een app, een API, een dashboard) via Traefik als reverse proxy bereikbaar maakt op mooie domeinnamen en automatisch HTTPS-certificaten (Let’s Encrypt) laat uitgeven en vernieuwen. Je krijgt een werkende basisopzet met Docker Compose, realistische commando’s, en uitleg over hoe Traefik “denkt”: routers, services, middlewares, entrypoints en providers.

Doelbeeld: je draait meerdere containers op één VPS en wilt ze via https://app.jouwdomein.nl, https://api.jouwdomein.nl, https://traefik.jouwdomein.nl publiceren, zonder zelf Nginx-configs te schrijven of certificaten te beheren.


Inhoud


1. Vereisten en concepten

Vereisten

Controleer Docker:

docker --version
docker compose version

Installeer (Ubuntu) indien nodig:

sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$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

Concepten: hoe Traefik routeert

Traefik is een edge router / reverse proxy die dynamisch routes ontdekt en verkeer doorstuurt:

Belangrijk: Traefik werkt label-gedreven. Je “configureert” routes door labels op containers te zetten.


2. DNS en poorten

DNS-records

Maak DNS-records aan die naar het publieke IP van je VPS wijzen:

Of gebruik een wildcard:

Controleer met:

dig +short app.jouwdomein.nl
dig +short traefik.jouwdomein.nl

Firewall / security group

Zorg dat inbound open staat:

Met UFW:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

3. Mappenstructuur en basisbestanden

We maken een projectmap, bijvoorbeeld:

mkdir -p ~/traefik-stack/{traefik,dynamic}
cd ~/traefik-stack

Aanbevolen structuur:

traefik-stack/
  docker-compose.yml
  traefik/
    traefik.yml
    acme.json
  dynamic/
    middlewares.yml

Waarom scheiden?

Maak acme.json met correcte rechten:

touch traefik/acme.json
chmod 600 traefik/acme.json

4. Traefik configureren (statisch en dynamisch)

4.1 Statische config: traefik/traefik.yml

Maak het bestand:

nano traefik/traefik.yml

Inhoud:

api:
  dashboard: true

log:
  level: INFO

accessLog: {}

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: "/dynamic"
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: "admin@jouwdomein.nl"
      storage: "/acme/acme.json"
      httpChallenge:
        entryPoint: web

Uitleg:

Alternatief: DNS-01 challenge (handig achter CGNAT of als poort 80 niet kan). Dat is provider-specifiek en buiten de scope van deze basisopzet.

4.2 Dynamische config: dynamic/middlewares.yml

Maak:

nano dynamic/middlewares.yml

Inhoud:

http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

    security-headers:
      headers:
        frameDeny: true
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: "no-referrer"
        stsSeconds: 15552000
        stsIncludeSubdomains: true
        stsPreload: true

    compress:
      compress: {}

    dashboard-auth:
      basicAuth:
        users:
          # Genereer met: htpasswd -nbB admin 'STERK_WACHTWOORD'
          - "admin:$2y$05$REPLACE_MET_BCRYPT_HASH"

Uitleg:

Installeer apache2-utils om htpasswd te gebruiken:

sudo apt update
sudo apt install -y apache2-utils

Genereer een bcrypt hash:

htpasswd -nbB admin 'KIES_EEN_STERK_WACHTWOORD'

Plak de output (na admin:) in middlewares.yml als volledige regel admin:<hash>.


5. Docker Compose: Traefik + voorbeeldservices

We maken één docker-compose.yml die Traefik en enkele demo-services start.

Maak:

nano docker-compose.yml

Voorbeeld (pas domeinen aan!):

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    command:
      - "--configFile=/etc/traefik/traefik.yml"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik/traefik.yml:/etc/traefik/traefik.yml:ro"
      - "./traefik/acme.json:/acme/acme.json"
      - "./dynamic:/dynamic:ro"
    networks:
      - proxy
    labels:
      - "traefik.enable=true"

      # Router voor dashboard (HTTPS)
      - "traefik.http.routers.traefik.rule=Host(`traefik.jouwdomein.nl`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"

      # Dashboard service (interne Traefik API)
      - "traefik.http.routers.traefik.service=api@internal"

      # Middlewares: auth + headers + compress
      - "traefik.http.routers.traefik.middlewares=dashboard-auth@file,security-headers@file,compress@file"

      # HTTP router alleen voor redirect naar HTTPS
      - "traefik.http.routers.traefik-http.rule=Host(`traefik.jouwdomein.nl`)"
      - "traefik.http.routers.traefik-http.entrypoints=web"
      - "traefik.http.routers.traefik-http.middlewares=redirect-to-https@file"

  whoami:
    image: traefik/whoami:v1.10
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"

      # HTTPS router
      - "traefik.http.routers.app.rule=Host(`app.jouwdomein.nl`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.routers.app.middlewares=security-headers@file,compress@file"

      # HTTP -> HTTPS redirect
      - "traefik.http.routers.app-http.rule=Host(`app.jouwdomein.nl`)"
      - "traefik.http.routers.app-http.entrypoints=web"
      - "traefik.http.routers.app-http.middlewares=redirect-to-https@file"

  api:
    image: nginx:alpine
    container_name: api-nginx
    restart: unless-stopped
    networks:
      - proxy
    volumes:
      - ./api-html:/usr/share/nginx/html:ro
    labels:
      - "traefik.enable=true"

      - "traefik.http.routers.api.rule=Host(`api.jouwdomein.nl`)"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls=true"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.routers.api.middlewares=security-headers@file,compress@file"

      - "traefik.http.routers.api-http.rule=Host(`api.jouwdomein.nl`)"
      - "traefik.http.routers.api-http.entrypoints=web"
      - "traefik.http.routers.api-http.middlewares=redirect-to-https@file"

networks:
  proxy:
    name: proxy
    driver: bridge

Belangrijke details in deze Compose

  1. Traefik expose’t 80 en 443 op de host
    Traefik is de enige container die direct aan de buitenwereld hangt. Andere containers hoeven geen host-poorten te publiceren.

  2. Alle services zitten op hetzelfde Docker network proxy
    Traefik kan de containers via dat netwerk bereiken.

  3. Labels definiëren routers/middlewares

    • traefik.http.routers.<naam>... bepaalt matchregels en TLS.
    • ...service=api@internal is speciaal voor het dashboard.
    • ...middlewares=...@file verwijst naar middlewares uit de file provider.
  4. HTTP router voor redirect
    Je maakt vaak twee routers per domein:

    • *-http op entrypoint web met redirect middleware.
    • * op entrypoint websecure met TLS.

Voeg simpele content toe voor de API-demo

Maak een map en een index:

mkdir -p api-html
cat > api-html/index.html <<'EOF'
<!doctype html>
<html>
  <head><meta charset="utf-8"><title>API demo</title></head>
  <body>
    <h1>API demo via Traefik</h1>
    <p>Als je dit ziet op https://api.jouwdomein.nl werkt de reverse proxy.</p>
  </body>
</html>
EOF

6. Starten, logs bekijken en valideren

Start de stack:

docker compose up -d

Check containers:

docker ps

Bekijk Traefik logs:

docker logs -f traefik

Verwacht o.a. regels over:

Test met curl (vanaf je laptop of server):

curl -I http://app.jouwdomein.nl
curl -I https://app.jouwdomein.nl
curl -I https://api.jouwdomein.nl

Open in browser:


7. HTTPS met Let’s Encrypt: hoe het werkt en valkuilen

7.1 ACME opslag (acme.json) en permissies

Traefik slaat certificaten op in acme.json. Dit bestand:

Controle:

ls -l traefik/acme.json

Moet ongeveer:

-rw------- 1 user user ... traefik/acme.json

7.2 HTTP-01 challenge en poort 80

Bij httpChallenge moet Let’s Encrypt jouw server op poort 80 kunnen bereiken. Veelvoorkomende problemen:

Check of iets anders luistert:

sudo ss -ltnp | grep -E ':80|:443'

Als Apache draait:

sudo systemctl stop apache2
sudo systemctl disable apache2

7.3 Rate limits en staging

Let’s Encrypt heeft rate limits. Als je veel test en faalt, kun je tijdelijk de staging CA gebruiken.

Voor staging voeg je in traefik.yml toe:

certificatesResolvers:
  letsencrypt:
    acme:
      caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
      email: "admin@jouwdomein.nl"
      storage: "/acme/acme.json"
      httpChallenge:
        entryPoint: web

Daarna:

docker compose restart traefik

Let op: staging certificaten zijn niet “trusted” in browsers. Gebruik dit alleen voor testen, en zet daarna terug naar productie (verwijder caServer) en wis eventueel acme.json om opnieuw te laten aanvragen:

docker compose down
rm -f traefik/acme.json
touch traefik/acme.json
chmod 600 traefik/acme.json
docker compose up -d

8. Middlewares: redirect, headers, basic auth, rate limiting

Middlewares zijn waar Traefik echt krachtig wordt. Je kunt ze:

8.1 Extra: rate limiting middleware

Voeg toe aan dynamic/middlewares.yml:

http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 50
        burst: 100

Koppel aan bijvoorbeeld de API-router in Compose:

- "traefik.http.routers.api.middlewares=security-headers@file,compress@file,rate-limit@file"

Toepassing: bescherm eenvoudige endpoints tegen overmatig verkeer. Dit is geen vervanging voor echte DDoS-bescherming, maar helpt tegen “per ongeluk” of simpele misbruikpatronen.

8.2 Path-based routing (meerdere apps op één domein)

Je kunt ook routes maken op basis van paths:

Voorbeeld router rule:

Host(`jouwdomein.nl`) && PathPrefix(`/api`)

Vaak wil je dan ook een stripPrefix middleware zodat je backend /api niet hoeft te kennen:

In middlewares.yml:

http:
  middlewares:
    strip-api-prefix:
      stripPrefix:
        prefixes:
          - "/api"

In labels:

- "traefik.http.routers.api.rule=Host(`jouwdomein.nl`) && PathPrefix(`/api`)"
- "traefik.http.routers.api.middlewares=strip-api-prefix@file,security-headers@file,compress@file"

Let op: path-based routing is handig, maar subdomeinen zijn vaak eenvoudiger qua cookies, CORS en scheiding.


9. Meerdere projecten/compose stacks en gedeelde proxy

In de praktijk wil je vaak:

Dat kan door een extern network te gebruiken.

9.1 Maak het proxy network één keer

Als je Traefik al draait met proxy network, bestaat die al. Anders:

docker network create proxy

9.2 In een ander compose project

Stel je hebt ~/project-x/docker-compose.yml:

services:
  projectx:
    image: ghcr.io/voorbeeld/projectx:latest
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.projectx.rule=Host(`projectx.jouwdomein.nl`)"
      - "traefik.http.routers.projectx.entrypoints=websecure"
      - "traefik.http.routers.projectx.tls=true"
      - "traefik.http.routers.projectx.tls.certresolver=letsencrypt"
      - "traefik.http.routers.projectx.middlewares=security-headers@file,compress@file"
      - "traefik.http.routers.projectx-http.rule=Host(`projectx.jouwdomein.nl`)"
      - "traefik.http.routers.projectx-http.entrypoints=web"
      - "traefik.http.routers.projectx-http.middlewares=redirect-to-https@file"

networks:
  proxy:
    external: true

Belangrijk:


10. Troubleshooting

10.1 “404 page not found” van Traefik

Traefik geeft 404 als geen router matcht. Check:

Handige inspectie:

docker inspect whoami | jq '.[0].Config.Labels'

(Installeer jq indien nodig: sudo apt install -y jq)

10.2 Certificaat wordt niet aangevraagd

Check Traefik logs op ACME errors:

docker logs traefik | grep -i acme

Veelvoorkomend:

10.3 “Bad Gateway” (502) of timeouts

Dit betekent: router matcht, maar backend is niet bereikbaar.

Check:

Commando’s:

docker ps
docker logs api-nginx
docker exec -it traefik sh -lc "wget -qO- http://api-nginx:80 | head"

Als de backend meerdere poorten heeft, kun je Traefik vertellen welke te gebruiken:

- "traefik.http.services.api.loadbalancer.server.port=80"

10.4 Dashboard werkt niet

Je moet:

Test:

curl -I https://traefik.jouwdomein.nl

Als je 401 krijgt is dat goed (auth actief). Als je 404 krijgt matcht de router niet.


11. Hardening en best practices

11.1 Zet het dashboard niet “open”

Het Traefik dashboard is handig, maar publiceer het alleen met:

IP allowlist middleware (voorbeeld) in middlewares.yml:

http:
  middlewares:
    allow-my-ip:
      ipAllowList:
        sourceRange:
          - "203.0.113.10/32"

Koppel aan dashboard router:

- "traefik.http.routers.traefik.middlewares=allow-my-ip@file,dashboard-auth@file,security-headers@file,compress@file"

11.2 Docker socket risico

Traefik gebruikt de Docker socket om containers te ontdekken. Dat is krachtig maar gevoelig: wie toegang heeft tot de socket kan veel op je host.

Mitigaties:

11.3 Updates en onderhoud

Update images:

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

11.4 Back-up van acme.json

Back-up traefik/acme.json. Als je dit verliest, vraagt Traefik opnieuw certificaten aan (kan rate limits raken).


Volgende stappen (uitbreidingen)

Als je basis werkt, zijn logische uitbreidingen:


Samenvatting

Je hebt nu:

Als je wilt, kan ik je huidige domeinen/containers (namen + gewenste URLs) laten doorvertalen naar concrete labels, of je helpen met een DNS-01 wildcard setup voor *.jouwdomein.nl.