Traefik v3: vijf apps op één Docker Compose file
Draai vijf applicaties achter één IP met Traefik v3 en Docker Compose. Leer hoe u Host-regels instelt, TLS automatiseert en de acme.json permissiefout bij opstarten voorkomt.
Eén IP, vijf applicaties, één poort 443
Uw VPS beschikt over één publiek IPv4-adres en één TCP-poort 443. U wilt hierop Gitea, een staging-kopie van uw applicatie, een intern dashboard, een statuspagina en een webhook-ontvanger draaien; vijf hostnamen, één server. Een reverse proxy is het proces dat poort 80 en 443 beheert, de Host-header van elk verzoek leest en dit doorstuurt naar de juiste container. Traefik voert deze taak uit en verkrijgt en vernieuwt automatisch een certificaat voor elke hostnaam, zonder dat u handmatig certbot hoeft uit te voeren. Nginx en Caddy kunnen deze vijf hostnamen eveneens uitstekend afhandelen. Als de keuze nog openstaat, is het de moeite waard om de drie proxies te vergelijken op basis van certificaatbeheer en configuratiekosten per applicatie voordat u alles aan één van deze systemen koppelt.
Het verschil tussen Traefik en een nginx server {}-blok zit in de herkomst van de configuratie. Bij nginx bewerkt u een bestand en voert u een reload uit, terwijl de levenscyclus van het certificaat een afzonderlijke taak blijft; dit is de workflow die u volgt wanneer u Let's Encrypt-certificaten uitgeeft met certbot op nginx, waarbij een vernieuwingstimer volledig buiten de webserver om functioneert. De Docker-provider van Traefik monitort de Docker-eventstream en leest labels van uw containers: start een container met een Host()-regel-label en deze is binnen een seconde bereikbaar; stop de container en de route verdwijnt. Dat is tevens het risico. Configuratie die in labels is opgeslagen, bevindt zich op vijf plaatsen tegelijk. Een onjuist label blijft onopgemerkt: de container wordt simpelweg niet gerouteerd en Traefik geeft geen foutmelding.
De vier begrippen
- Entrypoints zijn luisterende sockets. U definieert er twee:
webop:80enwebsecureop:443. - Routers matchen een verzoek (
Host(...)) en koppelen dit aan een service. Certificaten worden per router aangevraagd viatls.certresolver. - Services vormen de backend, een container en de poort waarop deze binnen het Docker-netwerk luistert.
- Middlewares bevinden zich tussen de router en de service: basic auth, IP-toegangslijsten, header-herschrijvingen en redirects.
Deze vier begrippen zijn de namen die Traefik hanteert voor taken die u anders handmatig zou uitvoeren: de router is een server_name, de service is een proxy_pass-doel, en de middlewares zijn de header- en auth-richtlijnen die u zelf instelt wanneer u handmatig een nginx reverse proxy server block opbouwt.
Statische configuratie (entrypoints, providers, ACME) wordt meegegeven via de command line van Traefik of in traefik.yml; wijzigingen hierin vereisen een herstart van Traefik. Dynamische configuratie (routers, services, middlewares) komt binnen via container-labels en wordt direct geladen zonder herstart. Het verwarren van deze twee is de meest voorkomende oorzaak van "mijn flag doet niets".
Het compose-bestand
Eén gedeeld Docker-netwerk genaamd proxy vormt de ruggengraat. Traefik bereikt een container alleen als beide zich op dit netwerk bevinden.
name: edge
networks:
proxy:
name: proxy
services:
traefik:
image: traefik:v3.5
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.network=proxy
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.le.acme.email=you@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
# while you iterate, point at staging so a mistake costs nothing:
# - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --api.dashboard=true
- --log.level=INFO
- --accesslog=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=le
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.middlewares=dashboard-auth
- traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REPLACE$$THIS
gitea:
image: gitea/gitea:1 # major-only pin keeps this demo copy-pasteable; pin an exact release in production
restart: unless-stopped
volumes:
- ./gitea:/data
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.gitea.rule=Host(`git.example.com`)
- traefik.http.routers.gitea.entrypoints=websecure
- traefik.http.routers.gitea.tls.certresolver=le
- traefik.http.services.gitea.loadbalancer.server.port=3000docker compose up -d, daarna docker compose logs -f traefik. Elke extra applicatie is een kopie van het gitea-blok met een eigen routernaam, een eigen Host() en een eigen interne poort. Een Nextcloud-installatie in Docker met TLS en back-ups past op dezelfde manier in het geheel: verwijder de gepubliceerde poorten, koppel de container aan proxy en laat de router-labels de hostnaam en het certificaat afhandelen.
Vijf details zijn hierbij van belang.
exposedByDefault=false maakt een container onzichtbaar voor Traefik totdat deze is voorzien van traefik.enable=true. Laat u dit weg, dan wordt voor elke container die u start, inclusief de tijdelijke postgres die u voor een snelle controle draaide, een route gegenereerd.
providers.docker.network=proxy vertelt Traefik welk netwerk moet worden gebruikt wanneer een container aan meerdere netwerken is gekoppeld. Laat u dit weg, dan kiest Traefik mogelijk het verkeerde container-IP, wat resulteert in een 502-fout die lijkt op een applicatiefout.
loadbalancer.server.port=3000 is de poort binnen de container; Gitea luistert daar op 3000. Merk op dat geen enkele applicatiecontainer een poort publiceert; alleen Traefik doet dit.
De redirect op het web-entrypoint zet plaintext-verzoeken om naar een 308 naar HTTPS. Poort 80 blijft hoe dan ook open: de ACME HTTP-challenge heeft deze nodig, evenals gebruikers die een kale hostnaam typen.
De dubbele $$ in de basic-auth-hash is Compose-escaping, geen typefout. Genereer deze met htpasswd -nbB admin 'your-password' (pakket apache2-utils) en verdubbel vervolgens elke $.
Het certificaat en de acme.json valkuil
tlschallenge=true selecteert TLS-ALPN-01: Let's Encrypt maakt verbinding met uw server op 443 en Traefik beantwoordt de challenge binnen de TLS-handshake. Het alternatief is HTTP-01, op poort 80; vervang de tlschallenge regel in de command: lijst van Traefik door deze twee:
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=webBeide opties werken. Beide vereisen dat de publieke DNS voor de hostnaam al naar uw VPS wijst, zodat de certificaatautoriteit de naam kan resolven en van buitenaf verbinding kan maken. Maak eerst het A (en AAAA) record aan, bevestig dit met dig +short git.example.com en start daarna pas Traefik.
Nu de valkuil die veel mensen een avond kost. Traefik bewaart zijn ACME-accountsleutel en elk uitgegeven certificaat in één acme.json. Als dat bestand leesbaar is voor de groep of voor iedereen, print Traefik een regel die sterk lijkt op deze en stopt het proces:
error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600De juiste oplossing is de bovenstaande: bind-mount de directory en laat Traefik het bestand zelf aanmaken met de juiste rechten. Als u acme.json heeft aangemaakt met touch, heeft uw umask het bestand waarschijnlijk op 644 gezet. Herstel dit op de host:
chmod 600 ./letsencrypt/acme.json
docker compose restart traefikMaak een back-up van die directory samen met uw applicatievolumes. Het verlies ervan is overkomelijk, aangezien certificaten opnieuw kunnen worden aangevraagd, maar het tegelijkertijd opnieuw aanvragen van vijf hostnames leidt tot het overschrijden van de rate limits.
Gebruik de staging CA tijdens het testen. Haal de caserver regel uit het commentaar, zorg dat elke route werkt, haal het commentaar daarna weer terug en verwijder acme.json zodat productiecertificaten vers worden aangevraagd. De productieomgeving van Let's Encrypt staat vijf dubbele certificaten per week toe voor een identieke set hostnames, en beperkt herhaalde mislukte validaties voor dezelfde naam. Staging geeft niet-vertrouwde certificaten uit; uw browser geeft een waarschuwing, en die waarschuwing is het signaal dat het werkt, met veel ruimere limieten.
Het dashboard is een beheerinterface, geen demo
De meeste quickstarts stellen --api.insecure=true in, waarmee het dashboard op poort 8080 wordt aangeboden zonder authenticatie. Op een server met een publiek IP-adres geeft dit uw routeringstopologie, hostnamen, middleware-namen en backend-poorten prijs aan iedereen die de server scant.
De labels op de traefik-service hierboven vormen het alternatief: het dashboard wordt gerouteerd zoals elke andere applicatie, via een echte hostnaam, over TLS, achter basicauth. service=api@internal is wat de router verbindt met de ingebouwde API van Traefik. Versterk dit verder door een IP-allowlist toe te voegen, die van links naar rechts wordt toegepast. Als uw kantooradres dynamisch is, stel het bereik dan in op het subnet dat wordt uitgegeven door een WireGuard VPN die u zelf host op dezelfde VPS en benader het dashboard alleen via de tunnel:
- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-authEén gedeeld basicauth-wachtwoord is niet langer verdedigbaar zodra vijf applicaties hun eigen accounts nodig hebben. Op dezelfde middleware-positie plaatst u een forwardauth die de beslissing overdraagt aan Authentik, een zelf-gehoste single sign-on server, zodat het dashboard en elke route daarnaast achter één login staan die u op één centrale plek kunt intrekken.
De Docker-socket is root
/var/run/docker.sock is een API die een container kan aanmaken die / vanaf de host mount. Toegang hiertoe staat gelijk aan root-toegang op de machine, en Traefik heeft dit nodig om labels te lezen.
Houd de :ro op de mount, maar wees duidelijk over wat dit oplevert: het maakt het socket-bestand alleen-lezen. Het stopt niet dat POST-verzoeken naar de Docker-API eroverheen worden verstuurd. De echte mitigatie is om Traefik nooit de socket te geven en er een filterende proxy tussen te plaatsen:
dockerproxy:
image: tecnativa/docker-socket-proxy # pin the current tag
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxyVerwijder het socket-volume uit Traefik en laat de provider naar de proxy wijzen:
--providers.docker.endpoint=tcp://dockerproxy:2375Traefik behoudt leestoegang tot containers en netwerken, en verliest de mogelijkheid om zaken aan te maken.
Firewall, poorten en de regel die iedereen verkeerd begrijpt
Twee poorten open, plus SSH:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableGepubliceerde poorten van Docker omzeilen ufw. Docker voegt eigen iptables-regels toe die vóór de chains van ufw worden geëvalueerd. Hierdoor is een container die is gestart met ports: ["3000:3000"] bereikbaar vanaf het internet, zelfs als ufw een deny-regel heeft ingesteld. De verdediging is structureel, niet een kwestie van firewallconfiguratie: publiceer poorten uitsluitend via Traefik en geef elke andere container networks: [proxy] en niets anders. Als een service daadwerkelijk de host moet bereiken, bind deze dan aan de loopback-interface, "127.0.0.1:3000:3000".
Probleemoplossing: fouten die u daadwerkelijk zult zien
404 page not found, geserveerd door Traefik. Geen enkele router kwam overeen. In volgorde van waarschijnlijkheid: de container mist traefik.enable=true (met exposedByDefault=false ingesteld); de Host()-regel komt niet overeen met de naam die u heeft getypt; de routernaam in het ene label verschilt van de routernaam in het andere (routers.gitea.rule en routers.gitea.entrypoints moeten hetzelfde woord zijn); of u heeft de hostnaam tussen aanhalingstekens geplaatst in plaats van backticks. Traefik v3 vereist backticks binnen matchers.
502 Bad Gateway. Een router kwam overeen, maar de backend was onbereikbaar. Bijna altijd bevindt de container zich niet op het proxy-netwerk; controleer docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. De andere kandidaat is een onjuiste loadbalancer.server.port: u heeft een gepubliceerde poort opgegeven, of de applicatie luistert elders. De logboeken benoemen de poging: dial tcp 172.18.0.5:8080: connect: connection refused.
De browser geeft een waarschuwing en het certificaat is uitgegeven aan TRAEFIK DEFAULT CERT. Er bestaat geen certificaat voor die hostnaam en Traefik heeft zijn zelfondertekende placeholder geserveerd. Lees de ACME-regels:
unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.comDNS wijst nog niet naar de server. Corrigeer het record, wacht de TTL af en herstart Traefik.
Invalid response from http://git.example.com/.well-known/acme-challenge/... bij de HTTP-challenge: poort 80 bereikt Traefik niet van buitenaf. Dit is meestal een firewall op providerniveau voor de VPS, niet ufw.
Certificaten worden nooit uitgegeven en uw DNS staat op Cloudflare met de oranje wolk ingeschakeld. Cloudflare beëindigt TLS aan de rand van hun netwerk en TLS-ALPN-01 kan hier niet doorheen worden voltooid. Zet het record op DNS-only tijdens het uitgeven, of schakel over naar de DNS-01-challenge met een API-token. DNS-01 is tevens de enige challenge die wildcards uitgeeft.
Redirect-loop. Iets vóór Traefik beëindigt al TLS en stuurt plaintext door naar :80; de redirect van het entrypoint stuurt het verkeer terug naar HTTPS. Verwijder een van de twee redirects.
Operationeel houden
De Docker-unit moet ingeschakeld zijn voor het opstarten (systemctl is-enabled docker), en restart: unless-stopped zorgt ervoor dat de stack na een reboot weer actief wordt. Voor expliciet beheer biedt een kleine systemd-unit die docker compose -f /srv/edge/compose.yml up -d uitvoert met RemainAfterExit=yes u systemctl status edge en controle over de opstartvolgorde.
Pin de Traefik-tag vast (traefik:v3.5, gebruik nooit latest). De upgrade van v2 naar v3 heeft de syntax van regels en providertitels gewijzigd, en een onbeheerde latest zal vrolijk een configuratie herladen die het niet langer begrijpt. Voer upgrades bewust uit: lees de migratienotities, verhoog de tag, docker compose up -d traefik, en monitor het logbestand. Als u nog op een v2-tag zit, leidt de migratiehandleiding van Traefik v2 naar v3 u door elke naamswijziging, de compatibiliteitsmodus en een rollback waarmee uw certificaten behouden blijven.
Maak een back-up van ./letsencrypt en het datavolume van elke applicatie. Traefik bevat geen andere status die u niet opnieuw kunt opbouwen vanuit het compose-bestand.
Wat breekt bij schaalvergroting
De eerste beperking is niet de doorvoersnelheid, maar de enkele server: één Traefik op één VPS is een single point of failure voor vijf applicaties. Omdat acme.json gebruikmaakt van flat-file opslag, zullen twee Traefik-instanties die hiernaar schrijven het bestand beschadigen. Schalen betekent dat de certificaatopslag moet worden verplaatst of dat TLS-termination elders moet plaatsvinden.
Het tweede punt betreft langdurige verbindingen. Server-sent events, grote uploads en trage clients bereiken de time-outlimieten van het entrypoint; --entryPoints.websecure.transport.respondingTimeouts.readTimeout en de bijbehorende writeTimeout en idleTimeout zijn de instellingen die u hiervoor kunt aanpassen. WebSockets werken zonder extra configuratie.
Het derde punt is schijfruimte. --accesslog=true schrijft naar stdout, en de json-file driver van Docker bewaart dit oneindig tenzij er limieten worden ingesteld. Configureer logging.options.max-size voor de Traefik-service, of schrijf het toegangslogboek naar een bestand en roteer dit.
Niets hiervan vereist een orchestrator. U heeft wel een server nodig die u zelf beheert, met een publiek IP-adres en poorten 80 en 443 open naar het internet; één kleine VPS is de volledige lijst met afhankelijkheden.
FAQ
Heb ik nog steeds certbot nodig als ik Traefik gebruik?
Nee. De ACME-resolver van Traefik vraagt voor elke hostname die het routeert certificaten aan en vernieuwt deze, waarbij alles wordt opgeslagen in acme.json. Certbot blijft het juiste hulpmiddel wanneer Nginx of een andere server zelf TLS-termination afhandelt; beide tegelijk gebruiken voor dezelfde hostnames verbruikt enkel onnodig de rate limits van Let's Encrypt.
Waarom geeft mijn container een 404 via Traefik?
Een 404 die door Traefik wordt geserveerd, betekent dat er geen enkele router overeenkwam met het verzoek. Controleer of de container is voorzien van traefik.enable=true (verplicht zodra exposedByDefault=false is ingesteld), of de waarde van Host() overeenkomt met de naam die u heeft ingevoerd, en of de routernaam identiek is in alle labels voor die applicatie. Traefik v3 vereist bovendien backticks binnen de matcher, geen aanhalingstekens.
Wat is het verschil tussen een 404 en een 502 in deze context?
Een 404 betekent dat er geen routing heeft plaatsgevonden; een 502 betekent dat een router wel overeenkwam, maar dat de backend de verbinding weigerde. De gebruikelijke oorzaken voor een 502 zijn een container die niet is gekoppeld aan het proxy-netwerk, of een loadbalancer.server.port die verwijst naar een gepubliceerde poort in plaats van de poort waarop de applicatie binnen de container luistert. Het toegangslogboek vermeldt het exacte adres dat Traefik heeft geprobeerd te bereiken.
Is het mounten van de Docker-socket als read-only voldoende?
De vlag :ro maakt het socketbestand alleen-lezen, niet de API erachter. POST-verzoeken worden nog steeds via de socket verwerkt en toegang tot de Docker API staat gelijk aan root-rechten op de host. De veiligere opstelling is de docker-socket-proxy-container zoals hierboven getoond, die alleen leesrechten voor containers en netwerken aan Traefik verleent en schrijfacties volledig blokkeert.
Kan Traefik een wildcard-certificaat uitgeven?
Alleen via de DNS-01 challenge, met een API-token voor uw DNS-provider. TLS-ALPN-01 en HTTP-01 valideren elk een enkele hostname en kunnen geen wildcard genereren. DNS-01 is ook de oplossing wanneer een CDN zoals Cloudflare TLS vóór uw VPS afhandelt en de andere twee challenges niet kunnen worden voltooid.