Neu veröffentlicht: E-Commerce mit Power Pages, Stripe & Analytics

· DevOps  · 6 minuten Lesezeit

Traefik statt NGINX für einen wachsenden Docker-Compose-Stack

Ab acht Services im Docker-Compose-Stack wird nginx.conf zur Wartungslast. Traefik liest Service-Konfiguration direkt aus Docker-Labels, terminiert TLS automatisch über ACME und braucht keine separate Konfigurationsdatei. Warum ich gewechselt habe und wie die Konfiguration aussieht.

Ab acht Services im Docker-Compose-Stack wird nginx.conf zur Wartungslast. Traefik liest Service-Konfiguration direkt aus Docker-Labels, terminiert TLS automatisch über ACME und braucht keine separate Konfigurationsdatei. Warum ich gewechselt habe und wie die Konfiguration aussieht.

Inhalt

Das Problem mit einem wachsenden Stack

Local Insight läuft in Docker Compose. Beim Start des Projekts waren es vier Services. Heute sind es acht: Backend, Frontend, Zitadel, Zitadel-Login-UI, MinIO, Redis, Qdrant und demnächst Postgres. Jeder neue Service, der nach außen erreichbar sein muss, braucht in NGINX einen eigenen server-Block in nginx.conf.

Das klingt nach Kleinkram, wird aber zum echten Problem, sobald mehrere Personen am Stack arbeiten oder die Konfiguration häufig angepasst wird. Die Konfiguration lebt an einem anderen Ort als der Service. Wer einen neuen Service in docker-compose.yml anlegt, muss gleichzeitig nginx.conf öffnen und bearbeiten. Die zwei Dateien driften auseinander.

Dazu kommt TLS. NGINX verwaltet kein eigenes Zertifikat. Das braucht einen Certbot-Sidecar, ein Cron-Renewal-Skript und eine Volume-Struktur für die Zertifikatsdateien. In Produktion ist das lösbar, aber es ist zusätzliche Infrastruktur, die gepflegt werden muss.

Ich habe Traefik ausprobiert und bin dabei geblieben.

Was Traefik anders macht

Traefik ist ein Cloud-Native-Reverse-Proxy, der für dynamische Container-Umgebungen gebaut wurde. Der entscheidende Unterschied zu NGINX: Traefik konfiguriert sich selbst aus Docker-Labels auf den Services.

Wenn ich einen neuen Service in docker-compose.yml anlege und ihm Labels mitgebe, weiß Traefik sofort, wie er erreichbar sein soll, welchen Port er verwendet und ob er hinter einem Middleware-Stack laufen soll. Keine separate Konfigurationsdatei, kein Neustart des Proxys.

Ein Vergleich für den konkreten Setup:

FaktorTraefikNGINX
Service-KonfigurationLabels im Compose-FileSeparater Server-Block in nginx.conf
Automatisches TLSEingebaut via ACME/Let’s EncryptCertbot-Sidecar + Cron-Renewal
Rate LimitingMiddleware als LabelSeparater limit_req_zone-Block
DashboardEingebaut, zeigt alle Routes liveNicht vorhanden
KonfigurationspflegeLabels liegen beim ServiceSeparate Datei wächst mit Service-Anzahl

NGINX ist die richtige Wahl, wenn ein einzelner Service maximal kontrolliert werden muss. Für einen Stack, der wächst, ist Traefik die bessere Entscheidung.

Konfiguration für lokale Entwicklung

Im lokalen Setup läuft Traefik ohne TLS. Das Dashboard ist auf Port 8085 erreichbar und zeigt alle erkannten Routes, Middlewares und Health-Status der Services.

Der Traefik-Service in docker-compose.override.yml:

traefik:
  image: traefik:v3.3
  command:
    # Enable Docker provider, do not expose containers by default
    - "--providers.docker=true"
    - "--providers.docker.exposedbydefault=false"
    - "--entrypoints.web.address=:80"
    # Enable API dashboard for local development
    - "--api.dashboard=true"
    - "--api.insecure=true"
  ports:
    - "80:80"
    - "8085:8080"
  volumes:
    # Read-only access to Docker socket for label discovery
    - "/var/run/docker.sock:/var/run/docker.sock:ro"
  networks:
    - app-network

Die Labels am Backend-Service:

backend:
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.backend.rule=Host(`api.localhost`)"
    - "traefik.http.routers.backend.entrypoints=web"
    - "traefik.http.services.backend.loadbalancer.server.port=3000"
    # IP-level rate limit: 60 requests/min, burst 20
    - "traefik.http.middlewares.backend-ratelimit.ratelimit.average=60"
    - "traefik.http.middlewares.backend-ratelimit.ratelimit.burst=20"
    - "traefik.http.routers.backend.middlewares=backend-ratelimit"

exposedbydefault=false ist wichtig: Traefik ignoriert Services, die kein traefik.enable=true-Label haben. Ohne das würde jeder neue Container im Netzwerk automatisch gerouted.

Konfiguration für Produktion

In Produktion kommen HTTPS und automatische Zertifikatsverwaltung dazu. Traefik spricht direkt mit der Let’s Encrypt-API über das ACME-Protokoll und speichert die Zertifikate in einem Docker-Volume.

traefik:
  image: traefik:v3.3
  command:
    - "--providers.docker=true"
    - "--providers.docker.exposedbydefault=false"
    # HTTP entry point redirects to HTTPS
    - "--entrypoints.web.address=:80"
    - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
    - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    - "--entrypoints.websecure.address=:443"
    # ACME/Let's Encrypt TLS resolver
    - "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
    - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - "/var/run/docker.sock:/var/run/docker.sock:ro"
    # Persistent volume for TLS certificates
    - "letsencrypt_data:/letsencrypt"
  networks:
    - app-network

Der Backend-Service bekommt in Produktion HTTPS-Labels:

backend:
  # No direct port binding — Traefik is the only entry point
  ports: []
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.backend.rule=Host(`${BACKEND_DOMAIN}`)"
    - "traefik.http.routers.backend.entrypoints=websecure"
    - "traefik.http.routers.backend.tls.certresolver=letsencrypt"
    - "traefik.http.services.backend.loadbalancer.server.port=3000"

ports: [] auf dem Backend-Service entfernt die direkte Host-Bindung. Der Node.js-Prozess ist von außen nur über Traefik erreichbar.

Was die IP-Level-Rate-Limit-Middleware bringt

Die ratelimit-Middleware in Traefik begrenzt Anfragen pro IP-Adresse, bevor sie Node.js überhaupt erreichen. Bei average=60 und burst=20 bedeutet das: 60 Anfragen pro Minute mit kurzem Burst-Spielraum.

Das ist Layer 1. Es stoppt Bots und einfache DDoS-Versuche auf Proxy-Ebene, ohne dass ein einziger JavaScript-Event-Loop-Tick verbraucht wird.

Was diese Schicht nicht kann: pro-Nutzer-Limits. Traefik hat keinen Zugriff auf den dekodierten JWT-Inhalt. Zwei Nutzer hinter derselben IP teilen sich das IP-Limit. Das ist korrekt für Missbrauchsschutz auf Netzwerkebene, aber nicht ausreichend für Quota-Enforcement auf Account-Ebene.

Dafür gibt es Layer 2. Den beschreibe ich im nächsten Artikel.

Was sich im Alltag ändert

Das Dashboard auf localhost:8085 zeigt live, welche Routes erkannt wurden, welche Middlewares aktiv sind und ob Services gesund sind. Wenn ein neuer Service Labels bekommt und Traefik neu geladen wird, erscheint die Route innerhalb von Sekunden im Dashboard.

Wenn ich in Produktion eine neue Domain einrichten will, füge ich dem Service zwei Labels hinzu. Traefik fragt Let’s Encrypt, bekommt das Zertifikat und die Domain ist HTTPS-fähig. Kein Certbot, kein Cron-Job, kein Zertifikatsdatei-Volume manuell anlegen.

Das ist der Hauptvorteil, den ich in der Praxis spüre: Der cognitive overhead für jede neue Route ist minimal.

Traefik als zentraler Einstiegspunkt vor dem Docker-Compose-Stack mit Label-basiertem Routing zu Backend und Frontend

Das Diagramm zeigt Traefik als einzigen externen Einstiegspunkt vor dem Stack. HTTP-Requests kommen auf Port 80 (lokal) oder 443 (Produktion) an. Traefik liest die Docker-Labels der Services und leitet Requests entsprechend weiter. Backend und Frontend haben keine direkte Host-Port-Bindung mehr in Produktion.


Alle Artikel der Serie

  1. Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen
  2. RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen
  3. AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen
  4. Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen
  5. Docker Compose Strategie: Override-Pattern, von lokal zu Azure: Artikel lesen
  6. Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen
  7. Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start: Artikel lesen
  8. tsconfig und Vite: Node16 vs. bundler, warum Vite eigene Regeln hat: Artikel lesen
  9. Instagram Caption mit MutationObserver vollständig laden: Artikel lesen
  10. Chrome Extension Foundation mit Health-Dot und Retry-Queue: Artikel lesen
  11. Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen
  12. Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen
  13. PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen
  14. Instagram Karussell vollständig erfassen mit MutationObserver: Lazy-Loading, Observer-before-click, Timeout-Fallback: Artikel lesen
  15. Notiz und Tags beim Screenshot-Speichern: Artikel lesen
  16. Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen
  17. Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen
  18. Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen
  19. PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen
  20. React Frontend mit react-oidc-context und Zitadel: Artikel lesen
  21. Vite Build-Time-Umgebungsvariablen in Docker: Artikel lesen
  22. Event-Driven Ingestion mit BullMQ und Redis: Artikel lesen
  23. MinIO statt Azurite: S3-kompatible Objektspeicherung lokal und auf Hetzner: Artikel lesen
  24. access_token, id_token und der Userinfo-Endpoint: was wohin gehört: Artikel lesen
  25. Qdrant Multi-Tenancy: Pro Nutzer eine eigene Collection: Artikel lesen
  26. Wenn Backend und Frontend unterschiedliche Typen kennen: Artikel lesen
  27. Zitadel Bootstrap entfernt: Host-Header-Bug und manuelles Setup: Artikel lesen
  28. Backend Code Review: sechs Probleme vor dem Launch behoben: Artikel lesen
  29. Traefik statt NGINX: Reverse Proxy für einen wachsenden Docker-Compose-Stack (dieser Artikel)
  30. Zweischichtiges Rate Limiting: Traefik und express-rate-limit mit Redis: Artikel lesen
  31. DSGVO Art. 17 korrekt implementieren: Promise.allSettled und Export-Batching: Artikel lesen
  32. Embedding-Modell-Lock-in: Warum mxbai-embed-large eine Produktionsentscheidung für immer ist: Artikel lesen
  33. Docker Volumes in Produktion: Named Volumes, Bind Mounts und der Hetzner-Volume-Trick: Artikel lesen
  34. Zwei Sicherheitslücken vor dem Launch: Redis ohne Auth und ein offener Qdrant-Admin-Port: Artikel lesen

Du baust gerade einen ähnlichen Multi-Service-Stack und fragst dich, wie du Routing und TLS sauber löst? Lass uns das gemeinsam einschätzen.

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen