Mastering Traefik v3 as a Reverse Proxy: Docker Configuration and Security
Traefik has established itself as the standard for container routing thanks to its dynamic discovery capability. In this article, we will dissect a Traefik v3.6 configuration, designed for a self-hosting environment, featuring automatic TLS encryption and authentication delegation.
The goal is not simply to copy-paste a YAML file, but to understand every directive, every label, and the underlying security strategy (specifically the use of a ForwardAuth).
1. Architecture and Key Concepts
Before analyzing the code, let’s recall Traefik’s role here: it acts as an Edge Router. It intercepts all incoming traffic (ports 80 and 443), handles TLS termination via Let’s Encrypt, and routes requests to the appropriate containers based on Docker Labels.
We will analyze the stack across three axes:
- Static configuration (the Traefik service itself).
- Security and Auth (the TinyAuth service).
- Application routing (how to expose your applications).
2. Commented Walkthrough: The Static Configuration
The static configuration defines how Traefik starts and connects to providers. Here is the analysis of the traefik service found in the docker-compose.yml.
The Traefik Service Block
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: always
# ...
We are using version v3.6 here. It is a best practice to pin the minor version to avoid unexpected breaking changes during a docker pull. Remember, “Latest is not a version!”
The Commands (Flags)
This is where the proxy’s behavior is defined. Let’s group them by theme for readability:
a) Observability and API
command:
- --log.level=INFO
- --metrics.prometheus=true
- --api.insecure=true
- --api=true
- Logs & Metrics: The
INFOlevel is standard. Enabling Prometheus allows scraping Traefik to monitor entrypoints and routers. - API & Dashboard:
api.insecure=trueenables the dashboard on port 8080 without authentication by default. - Point of attention: In production, we often prefer to disable
insecuremode and route the dashboard through Traefik itself with an auth middleware. However, we will see below that access is restricted via port mapping.
b) Providers (Dynamic Discovery)
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.file.directory=/traefik_dynamic
- --providers.file.watch=true
- Docker: Traefik listens to the Docker socket.
- ExposedByDefault=false: Crucial for security. By default, no container is exposed. You must explicitly add the label
traefik.enable=trueon each service you wish to publish. - File Provider: Allows loading dynamic configurations (global middlewares, custom TLS certificates) from the
/traefik_dynamicdirectory.
c) EntryPoints and HTTPS Redirection
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
A canonical configuration: all traffic arriving on port 80 (web) is immediately redirected to 443 (websecure) via HTTPS. This forces a “Secure by Default” policy.
d) Certificates and ACME (Let’s Encrypt)
- --certificatesresolvers.myresolver.acme.httpchallenge=true
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.myresolver.acme.caserver=[https://acme-v02.api.letsencrypt.org/directory](https://acme-v02.api.letsencrypt.org/directory)
- --certificatesresolvers.myresolver.acme.email=benjamin.xxxxx@gmail.com
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
- Resolver: Named
myresolver. - Challenge: Uses
httpChallenge. Traefik will temporarily serve a file on port 80 to prove to Let’s Encrypt that it owns the domain. - Technical note: The
httpChallengeis simple but does not allow generating Wildcard certificates (*.domain.fr), which would require adnsChallenge. - Storage: The
acme.jsonfile (persistent via volume) contains the private keys.
e) Access Logs (Specific Configuration)
- --accesslog=false
- --accesslog.format=json
- --accesslog.fields.defaultmode=keep
# ... (other filters)
- Observation: We note a detailed field configuration (
User-Agentkept,ServiceURLdropped), but the global directive is--accesslog=false. - Consequence: Currently, no access logs are generated. To enable them, simply switch this flag to
true. I plan to retrieve the logs in json format and process them through an ELK stack.
Ports and Networks
ports:
- 80:80
- 10.8.0.1:8080:8080/tcp
- 443:443
- Specific Binding: Note the line
10.8.0.1:8080:8080/tcp. This is a security measure compensating for the--api.insecure=trueflag. The dashboard is only accessible via the network interface with IP10.8.0.1(my Wireguard VPN), and not exposed publicly.
networks:
- web
- back
Traefik is connected to two networks:
web: The public/DMZ network where front-ends are located.back: To reach services that should not be directly exposed but that Traefik must route.
3. Security with TinyAuth (ForwardAuth)
The compose file includes a lightweight authentication service: TinyAuth.
tinyauth:
image: ghcr.io/steveiliop56/tinyauth:v3
# ... env vars ...
labels:
- traefik.enable=true
# Router definition to access the TinyAuth interface
- traefik.http.routers.tinyauth.rule=host(`tinyauthv2.bracloud.fr`)
- traefik.http.routers.tinyauth.entrypoints=websecure
- traefik.http.routers.tinyauth.tls.certresolver=myresolver
# MIDDLEWARE definition
- traefik.http.middlewares.tinyauth.forwardauth.address=http://tinyauth:3000/api/auth/traefik
The ForwardAuth Mechanism
This is where the magic happens. The last line of the labels defines a Middleware named tinyauth (implicitly attached to the service container, but globally usable).
The flow is as follows:
- A request arrives at a protected application.
- Traefik sees the
tinyauthmiddleware. - Traefik pauses the request and contacts
http://tinyauth:3000/.... - If TinyAuth responds with 200 OK, Traefik lets the request pass to the application. Otherwise, it redirects to the login.
4. Integration Examples (How to use this stack?)
The configuration above is the foundation. Here is how to deploy applications behind this Traefik instance.
A. Minimal Example (Whoami)
To expose a simple service on the web network.
services:
whoami:
image: traefik/whoami
networks:
- web
labels:
# 1. Explicit activation (because exposedbydefault=false)
- "traefik.enable=true"
# 2. Routing rule
- "traefik.http.routers.whoami.rule=Host(`whoami.bracloud.fr`)"
# 3. HTTPS Entrypoint and Certificate
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
B. Advanced Example: Protected Internal Service
Imagine a sensitive business application (internal-app) located on the back network that we want to protect with TinyAuth.
services:
webapp:
image: my-company/app
networks:
- back
labels:
- "traefik.enable=true"
# Tell Traefik which network to use to contact the container
# (Necessary because Traefik is on 'web' and 'back', but the app is only on 'back')
- "traefik.docker.network=back"
- "traefik.http.routers.app-sec.rule=Host(`admin.bracloud.fr`)"
- "traefik.http.routers.app-sec.entrypoints=websecure"
- "traefik.http.routers.app-sec.tls.certresolver=myresolver"
# APPLYING THE AUTH MIDDLEWARE
# Syntax: middleware-name@provider
- "traefik.http.routers.app-sec.middlewares=tinyauth@docker"
Important Note: The middleware is named
tinyauthand was defined in the labels of thetinyauthcontainer (therefore via thedockerprovider). We must call it viatinyauth@docker.
5. Vigilance Points & Debugging
If you deploy this stack, here are the critical points to verify to avoid “404 Page Not Found” or “Bad Gateway” errors.
- External Networks: The
docker-compose.ymldeclares thewebandbacknetworks asexternal: true. You must create them before launching the stack:
docker network create web
docker network create back
acme.jsonPermissions: The mounted file/etc/letsencrypt/acme.json(or the folder) must have correct permissions (usuallychmod 600) so that Traefik can write private keys to it.- DNS: The
httpChallengerequires that the domain (e.g.,tinyauthv2.bracloud.fr) points to the server’s public IP and that port 80 is open and redirected to this container. - Port Conflict: Verify that no other process (Apache, System Nginx) is already listening on port 80 or 443 of the host machine.
Conclusion
This Traefik v3.6 configuration is robust. It cleanly separates responsibilities: Traefik handles cryptography and routing, TinyAuth handles identity, and Docker handles the application lifecycle.
The use of --providers.docker.exposedbydefault=false and binding the dashboard to a specific IP (10.8.0.1) demonstrates a “Security by Design” approach.
Actuellement ingénieur DevOps/Architecte Cloud, j’étais initialement interessé par l’administration système et grâce aux entreprises dans lesquelles j’ai pu travailler Oxalide et maintenant Claranet j’ai eu la chance de découvrir l’univers du Cloud et de l’automatisation.
Je me suis décidé a publier ce blog pour vous faire partager ma passion mais également pour enrichir avec modestie tout ce que l’on peut trouver sur internet. Bonne lecture !