· David Göschel · DevOps · 5 minuten Lesezeit
Traefik als einziger Einstiegspunkt im Docker Compose Stack
Ich habe den Stack so umgebaut, dass Browser und Extension nur noch Traefik sehen. Der Rest läuft über zwei Docker Netze, klare Labels und keine direkten Host-Ports mehr.

Inhalt
- Traefik als einziger Einstiegspunkt im Docker Compose Stack
- Die neue Topologie
- Was auf welches Netz gehört
- Wie Traefik die Routes erkennt
- Warum das für Hetzner gut passt
- Was ich aus dem Traefik Einstiegspunkt gelernt habe
- Alle Artikel der Serie
Traefik als einziger Einstiegspunkt im Docker Compose Stack
Am Anfang war der Stack noch simpel genug, um ihn direkt zu verstehen. Frontend auf einem Port, Backend auf einem Port, Zitadel auf einem Port. Lokal war das bequem. Für eine kurze Demo war das auch völlig in Ordnung.
Genau an diesem Punkt entsteht aber oft der erste Denkfehler. Ein Port ist kein Architekturmodell. Ein Port ist nur ein sichtbarer Einstieg. Sobald mehrere Services dazukommen, wird aus einer bequemen Abkürzung schnell ein System, das sich nur noch über Sonderfälle erklären lässt.
Ich habe den Stack deshalb auf eine klarere Form umgestellt. Es gibt nur noch einen öffentlichen Einstiegspunkt. Alles andere läuft intern.
Die neue Topologie
Die Zielarchitektur ist einfach.
Browser
Browser Extension
│
▼
Traefik
│
┌──────┼─────────────┐
▼ ▼ ▼
Frontend Backend ZitadelDas heißt in der Praxis:
- Der Browser kennt nur
app.localhost,api.localhostundauth.localhost. - Die Extension sendet Daten nur an
http://api.localhost/ingest. - Traefik entscheidet, welcher Container die Anfrage bekommt.
- Interne Services wie Redis, Qdrant, Postgres und Ollama bleiben komplett privat.
Die eigentliche Trennung passiert über zwei Docker Netze.
networks:
traefik_net:
name: traefik_net
internal_net:
name: internal_nettraefik_net ist für alles, was Traefik routen muss. internal_net ist für Infrastruktur, die niemals direkt nach außen gehört.
Was auf welches Netz gehört
Die Regel ist einfach.
- Frontend gehört nur auf
traefik_net. - Backend gehört auf
traefik_netundinternal_net. - Zitadel gehört auf
traefik_netundinternal_net. - MinIO gehört auf beide Netze.
- Redis, Qdrant, Postgres und Ollama gehören nur auf
internal_net.
So bleibt das Frontend sauber getrennt. Es kann keine Datenbank direkt sehen. Es kann nur mit Traefik sprechen. Das ist genau die Art von Begrenzung, die später auch auf Hetzner noch Sinn ergibt.
Wie Traefik die Routes erkennt
Traefik liest seine Konfiguration direkt aus Docker Labels.
frontend:
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`app.localhost`)"
- "traefik.http.routers.frontend.entrypoints=web"
- "traefik.http.services.frontend.loadbalancer.server.port=80"Das gleiche Muster gilt für Backend und Zitadel. Dadurch liegt die Route direkt beim Service. Kein zweites Konfigurationssystem. Keine verstreuten Sonderfälle.
Für mich ist das der eigentliche Gewinn. Wenn ich später einen neuen Dienst ergänze, muss ich nicht erst drei Dateien durchsuchen, um herauszufinden, wo die Route definiert wird. Der Dienst trägt seine Route selbst.
Warum das für Hetzner gut passt
Die Struktur ist lokal schon so aufgebaut, wie ich sie später in Produktion weiterverwenden kann.
Das bedeutet:
- Die Domains ändern sich, nicht die Architektur.
- HTTP wird später zu HTTPS.
- Traefik bleibt die zentrale Schicht davor.
- Interne Service Kommunikation bleibt gleich.
Wenn ich also später app.localhost durch app.example.com ersetze, muss ich keine Infrastruktur neu erfinden. Ich ändere nur die Domainwerte und die Zertifikatskonfiguration.
Das ist genau die Art von Vorarbeit, die ein Projekt später ruhig macht.
Was ich aus dem Traefik Einstiegspunkt gelernt habe
Ein Compose Stack wird nicht dadurch produktionsreif, dass er lokal startet. Er wird produktionsreif, wenn die Topologie schon die richtigen Grenzen kennt.
Ich brauche dafür kein größeres Tooling. Ich brauche zuerst ein sauberes Modell.
Traefik ist dabei nicht nur ein Reverse Proxy. Es ist die Schicht, die aus einzelnen Containern ein lesbares System macht.

Die Grafik zeigt den einen öffentlichen Einstiegspunkt vor dem Stack. Browser und Extension sprechen nur mit Traefik. Die internen Services bleiben auf privaten Docker Netzen.
Alle Artikel der Serie
- Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen
- RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen
- AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen
- Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen
- Docker Compose Strategie: Override-Pattern, von lokal zu Azure: Artikel lesen
- Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen
- Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start: Artikel lesen
- tsconfig und Vite:
Node16vs.bundler, warum Vite eigene Regeln hat: Artikel lesen - Instagram Caption mit MutationObserver vollständig laden: Artikel lesen
- Chrome Extension Foundation mit Health-Dot und Retry-Queue: Artikel lesen
- Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen
- Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen
- PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen
- Instagram Karussell vollständig erfassen mit MutationObserver: Lazy-Loading, Observer-before-click, Timeout-Fallback: Artikel lesen
- Notiz und Tags beim Screenshot-Speichern: Artikel lesen
- Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen
- Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen
- Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen
- PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen
- React Frontend mit react-oidc-context und Zitadel: Artikel lesen
- Vite Build-Time-Umgebungsvariablen in Docker: Artikel lesen
- Event-Driven Ingestion mit BullMQ und Redis: Artikel lesen
- MinIO statt Azurite: S3-kompatible Objektspeicherung lokal und auf Hetzner: Artikel lesen
- access_token, id_token und der Userinfo-Endpoint: was wohin gehört: Artikel lesen
- Qdrant Multi-Tenancy: Pro Nutzer eine eigene Collection: Artikel lesen
- Wenn Backend und Frontend unterschiedliche Typen kennen: Artikel lesen
- Zitadel Bootstrap entfernt: Host-Header-Bug und manuelles Setup: Artikel lesen
- Backend Code Review: sechs Probleme vor dem Launch behoben: Artikel lesen
- Traefik statt NGINX: Reverse Proxy für einen wachsenden Docker-Compose-Stack: Artikel lesen
- Zweischichtiges Rate Limiting: Traefik und express-rate-limit mit Redis: Artikel lesen
- DSGVO Art. 17 korrekt implementieren: Promise.allSettled und Export-Batching: Artikel lesen
- Embedding-Modell-Lock-in: Warum mxbai-embed-large eine Produktionsentscheidung für immer ist: Artikel lesen
- Docker Volumes in Produktion: Named Volumes, Bind Mounts und der Hetzner-Volume-Trick: Artikel lesen
- Zwei Sicherheitslücken vor dem Launch: Redis ohne Auth und ein offener Qdrant-Admin-Port: Artikel lesen
- Traefik als einziger Einstiegspunkt im Docker Compose Stack: (dieser Artikel)
- Zitadel hinter Traefik richtig verdrahten mit Issuer, JWKS und Login V2: Artikel lesen
- Frontend reparieren wenn der nginx Healthcheck an localhost scheitert: Artikel lesen
- Observability für meinen Docker Compose Stack mit Bull Board und Dozzle: Artikel lesen
- Qdrant Dashboard sicher öffnen mit lokalem Traefik und SSH Tunnel: Artikel lesen
- Diagnose: Warum mein Chunking trotz Tokenisierung noch scheiterte: Artikel lesen
- Entscheidung: Warum ich den Chunk auf 1500 Tokens gesetzt habe: Artikel lesen
- Implementierung: Wie ich den Embedding Workflow in mehrere saubere Schritte zerlegt habe: Artikel lesen
- Validierung: Wie ich Chunking, Speicherung und Suche wieder zusammenbringe: 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.



