Traefik Proxy 2.x and Docker 101
Originally published: October 2019
Updated: May 2022
Docker friends, welcome!
Today, I decided to dedicate some time to walk you through several changes that were introduced in Traefik Proxy 2.x versions. I’ll be using practical and common scenarios, and hopefully, this article will help you understand every concept there is to know, and you’ll keep learning by yourself, discovering tips and tricks to share with the community!
Before I go further, I assume for this article that you already have a Docker setup using Traefik 2.x. Since I like to use docker-compose files for basic demonstrations, I’ll use the following base compose file:
version: "3.3"
services:
traefik:
image: "traefik:v2.6"
command:
- --entrypoints.web.address=:80
- --providers.docker=true
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
my-app:
image: traefik/whoami:v1.7.1
```
The full compose file is available here.
I include nothing fancy here! I declare an entrypoint (web
for port 80
), enabled the Docker provider, attach our Traefik container to the needed ports, and make sure I can listen to Docker through the socket. I also have an application my-app
which I’ll expose later.
Note: You can get the examples from our repository if you want to play with them. (Yes, I know how dangerous it can be to copy/paste YAML 😆). Also, note that for this example I’m relying on Traefik Proxy 2.6.
Now that you’re all set, let’s start!
Let’s enable the dashboard
Because I always enjoy seeing what I’m working on, let’s first enable the Traefik dashboard in development mode. All you need to do is to add one argument to the Traefik command itself:
services:
traefik:
image: "traefik:v2.6"
command:
- --entrypoints.web.address=:80
- --providers.docker
- --api.insecure # Don't do that in production# ...
The full compose file is available here.
There you are! By adding --api.insecure
you’ve enabled the API along with the dashboard. But beware, in this first step, you’ve enabled the insecure development mode — don’t do that in production!
Of course, you’ll see at the end of the article how to enable a secured dashboard, but for now, you can enjoy and see it at localhost:8080/dashboard/
.
My application handles requests on "example.com"
If you only need to route requests to my-app
based on the host, then attach one label to your container, and that’s it!
services:
my-app:
image: traefik/whoami:v1.7.1
labels:
- traefik.http.routers.my-app.rule=Host(`example.com`)
The full compose file is available here.
This label means “Hey Traefik! (traefik.
). This HTTP router (http.routers.
) I call my-app (my-app.
), must catch requests to example.com (rule=Host(example.com)
).”
More details (optional)
Routers in Traefik Proxy define the routes that connect your services to the requests, and you use rules to define what makes the connection. This is the reason why you see routers
in the label, as well as rule
.
Since Traefik Proxy supports both TCP and UDP, it wants to know what kind of protocol you’re interested in, which explains the http
keyword in the label.
My application listens on a specific port
What happens if your application listens on a different port than the default :80
? Let’s say it listens on :8082
. You build on the previous example and add (again) one label.
services:
my-service:
image: traefik/whoami:v1.7.1
command:
- --port=8082 # Our service listens on 8082labels:
- traefik.http.routers.my-app.rule=Host(`example.com`)
- traefik.http.services.my-app.loadbalancer.server.port=8082
The full compose file is available here.
This label means, “Hey Traefik! (traefik.
) This HTTP service (http.services.
) I call my-app (my-app.
) will load balance incoming requests between servers (.server
) that listen on port 8082 (.port=8082
).”
More details (optional)
Services in Traefik Proxy are the 0 for the routes. They usually define how to reach your programs in your cluster. The LoadBalancer type is a round-robin between all the available instances (called server
). By default, Traefik Proxy considers that your program is available on the port exposed by the Dockerfile of your program, but you can change that by explicitly defining the port.
Since we specify only one service in the example, there is no need to define the target of the previously defined router explicitly.
Note: The—-port=8082
command is specific to our whoami
application and has nothing to do with Traefik Proxy. It tells whoami
to start listening on 8082, so you can simulate your use case.
I need BasicAuth (or any piece of middleware)
Once Traefik Proxy has found a match for the request, it can process it before forwarding it to the service. In the following example, I add a BasicAuth mechanism for my route. This is done with two additional labels.
services:
my-svc:
image: traefik/whoami:v1.7.1
labels:
- traefik.http.routers.my-app.rule=Host(`example.com`)
- traefik.http.routers.my-app.middlewares=auth
- traefik.http.middlewares.auth.basicauth.users=test:xxx
The full compose file is available here.
The first label means, “Hey Traefik! (traefik.
) My HTTP router is called my-app
, remember? (http.routers.my-app.
) I’d like to attach to it a piece of middleware named auth (.middlewares=auth
).”
Of course, since you haven’t yet declared the auth
middleware, you need to be a bit more explicit. The second label means, “Hey Traefik! (traefik.
) Let’s talk about an HTTP middleware (http.middlewares.
) I call auth (auth.
). It’s a BasicAuth middleware (basicauth.
). Since you probably need users to know who can do what, here is the users’ list (.users=test:xxx
).”
Note: All dollar signs in the password should be double escaped, for example echo $(htpasswd -nB user) | sed -e s/\\\\$/\\\\$\\\\$/g
.
More details (optional)
Middleware in Traefik Proxy is a way to define behaviors and tweak the incoming request before forwarding it to the service. Since they act before the request is forwarded, they are attached to Routers. You can define middleware and reuse them as many times as you like (this is why you need to name them, in this example, auth
). There are many kinds of middleware, and BasicAuth is one of them. Each middleware has different parameters to define its behaviors (in this example, I define the users’ list).
I need HTTPS
With Traefik Proxy, enabling automatic certificate generation is a matter of four lines of configuration, and enabling HTTPS on your routes is a matter of two lines of configuration.
1. Enabling automatic certificate generation
I’ll introduce a little tip here — since Traefik Proxy is launched as a container, I’ll attach labels to it for common configuration options. What is specific to other containers will, of course, stay on other containers; I’m not messy, people!)
services:
traefik:
image: "traefik:v2.6"
command:
- --entrypoints.websecure.address=:443
# ...- [email protected]
- --certificatesresolvers.le.acme.storage=/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
# ...ports:
# ...- "443:443"
The full compose file is available here.
You’ve already seen the first command-line given to Traefik Proxy. This means, “I have an entrypoint (entrypoints.
) I call websecure (websecure.
) that uses port 443 (.address=:443
).” And since Traefik Proxy now listens to 443, you need to tell Docker that it should bind external port 443 to your service’s port 443 ("443:443"
).
Now, the others are a bit trickier, but nothing too crazy if you’ve had time to drink your coffee (or tea). The first says, “I’d like a mechanism to generate certificates for me (certificatesresolvers.
) that I’ll call le (le.
). It’s an ACME resolver (acme.
), my account there is [email protected]
([email protected]
).” (Disclaimer: not my real email address, don’t try it!)
The second says, “This mechanism named le
I told you about, the ACME stuff (certificatesresolvers.le.acme.
), it will save the certificates in the file /acme.json
(storage=/acme.json
).”
And the third is your inner geek speaking, “Since the le
mechanism I defined before (certificatesresolvers.le.acme.
) supports different challenges for certificate generation, I’ll choose … the TLS challenge (tlschallenge=true
).”
That was a bit more text than usual, but here you are: you have a fully functional mechanism to generate/renew certificates for you!
More details (optional)
Certificate Resolvers in Traefik Proxy are a system that handles certificate generation, renewal, and disposal for you. They detect the hosts you’ve defined for your routers and get the matching certificates.
Currently, Certificate Resolvers use Let’s Encrypt to obtain certificates. You are expected to configure your account (which is basically your email address). In order to prove to Let’s Encrypt that you’re the owner of the domains you’re requesting certificates for, LE will give Traefik Proxy a challenge. There are multiple possible challenges. In my example here, I chose the TLSChallenge. In the documentation, you’ll find a description of the other challenges (dnsChallenge and httpChallenge).
Advanced users can define multiple Certificates Resolvers using different challenges, and they can use them to generate wildcards. But that’s another story!
2. Enabling automatic certificate generation
Now that you have a mechanism to generate certificates for you, let’s leverage it to enable HTTPS on your route. You’ll only need two labels.
my-app:
image: traefik/whoami:v1.7.1
labels:
- traefik.http.routers.my-app.rule=Host(`example.com`)
- traefik.http.routers.my-app.middlewares=auth
- traefik.http.routers.my-app.tls.certresolver=le
- traefik.http.routers.my-app.entrypoints=websecure
The full compose file is available here.
The first label means, “Hey Traefik! (traefik.
) My HTTP router (http.routers.
) I call my-app (my-app.
) uses TLS and the Certificate Resolver named le (certresolver=le
).”
And the second says, “Traefik! (traefik.
) this router, you know? (http.routers.my-app
) It will only listen to the entrypoint I call websecure **(entrypoints=websecure
).”
More details (optional)
Traefik Proxy allows you to define TLS termination directly on your routers!
Also, by default, routers listen to every known entrypoints. In this example, I wanted Traefik Proxy to limit the use of HTTPS on port 443
, which is the reason why I told the router to listen only to websecure (defined to port 443 with entrypoints.websecure.address=:443
)
I want HTTPS redirection!
Now that you have HTTPS routes, let’s redirect every non-HTTPS request to its HTTPS equivalent. For that, you’ll reuse the previous trick and add just four labels to declare a redirect middleware and a catch-all router for unsecured routes.
services:
traefik:
image: "traefik:v2.6"
# ...
labels:
# ...
# middleware redirect
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
# global redirect to https
- "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.redirs.entrypoints=web"
- "traefik.http.routers.redirs.middlewares=redirect-to-https"
The full compose file is available here.
The first label means, “Hey Traefik! (traefik.
) let’s declare an HTTP middleware (http.middlewares.
) we’ll call redirect-to-https (redirect-to-https.
). It’s a RedirectScheme middleware (redirectscheme.
) that will force the scheme to HTTPS (scheme=https
).”
Then, let’s see the router part, “Hey Traefik! (you know the drill) (traefik.
) I have an HTTP router (http.routers.
) I’ll call redirs (redirs.
) that will match requests on any host (rule=hostregexp({host:.+})
) Yes sir! I’m insane and will catch everything, that’s how greedy I am.”
Then, I add, “Hey Traefik! (traefik.
) I was kidding … the redirs HTTP router (http.routers.redirs.
) won’t catch everything but just requests on port 80 (entrypoints=web
).”
Finally, I add the redirect middleware to the router. “Traefik? (traefik.
) On the redirs HTTP router (http.routers.redirs.
) we’ll add the redirect-to-https middleware (middlewares=redirect-to-https
).”
More details (optional)
Just kidding! By now, you’ve seen everything there is to know, so no additional details to include here.
Compiling everything to secure your dashboard!
Now that you’ve manipulated every important notion (Entrypoints, Routers, Middleware, Services, Certificates Resolvers & TLS), you can combine them to obtain a secure dashboard!
version: "3.3"
services:
traefik:
image: "traefik:v2.6"
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --providers.docker
- --api
- [email protected]
- --certificatesresolvers.le.acme.storage=/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./acme.json:/acme.json"
labels:
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`api.example.com`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=admin"
- "traefik.http.routers.traefik.tls.certresolver=le"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.middlewares.admin.basicauth.users=admin:xxx"
# ...
The full compose file is available here.
First, I remove the insecure API (specifying --api
instead of --api.insecure
). Then, I tell Traefik (traefik.
) to add an HTTP router called traefik (http.routers.traefik.
) catching requests on api.example.com (rule=Host(api.example.com)
).
This router (traefik.http.routers.traefik.
) will forward requests to a service called api@internal (service=api@internal
), using a middleware named admin (middlewares=admin
), and tls (tls=true
) with a certresolver called le (tls.certresolver=le
).
Finally, I declare the admin middleware (traefik.http.middlewares.admin.basicauth.users=admin:xxx
).
More details (optional)
The only subtle thing to know is that when you enable the API, in default mode, it creates an internal service called api@internal
. It’s up to you to properly secure it.
Questions? Where to go next?
Hopefully, I’ve gone through all the important questions you’ll have when dealing with Traefik Proxy 2.x in a Docker setup. I hope this article brings many answers.
If you're interested in learning more about using Traefik Proxy as an ingress proxy and load balancer, watch our recent webinar “Explore All the New Features of Traefik Proxy 2.6.”
In the meantime — happy networking!