Let's play with Traefik
I’ve been playing around with containers for a few years now. I find them very useful. If you host your own, like I do, you probably write a lot of nginx configurations, maybe apache.
If that’s the case, then you have your own solution to get certificates. I’m also assuming that you are using let’s encrypt with certbot or something.
Well, I didn’t want to anymore. It was time to consolidate. Here comes Traefik.
Traefik
So Traefik is
an open-source Edge Router that makes publishing your services a fun and easy experience. It receives requests on behalf of your system and finds out which components are responsible for handling them.
Which made me realize, I still need nginx somewhere. We’ll see when we get to it. Let’s focus on Traefik.
Configuration
If you run a lot of containers and manage them, then you probably use docker-compose.
I’m still using version 2.3
, I know I am due to an upgrade but I’m working on it slowly.
It’s a bigger project… One step at a time.
Let’s start from the top, literally.
---
version: '2.3'
services:
Note
Upgrading to version 3.x
of docker-compose requires the creation of network to link containers together. It’s worth investing into, this is not a docker-compose tutorial.
Then comes the service.
traefik:
container_name: traefik
image: "traefik:latest"
restart: unless-stopped
mem_limit: 40m
mem_reservation: 25m
and of course, who can forget the volume mounting.
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
Design
Now let’s talk design to see how we’re going to configuse this bad boy.
I want to Traefik to listen on ports 80
and 443
at a minimum to serve traffic.
Let’s do that.
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
and let’s not forget to map them.
ports:
- "80:80"
- "443:443"
Next, we would like to redirect http
to https
always.
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
We are using docker, so let’s configure that as the provider.
- --providers.docker
We can set the log level.
- --log.level=INFO
If you want a dashboard, you have to enable it.
- --api.dashboard=true
And finally, if you’re using Prometheus to scrape metrics… You have to enable that too.
- --metrics.prometheus=true
Let’s Encrypt
Let’s talk TLS. You want to serve encrypted traffic to users. You will need an SSL Certificate.
Your best bet is open source. Who are we kidding, you’d want to go with let’s encrypt.
Let’s configure acme to do just that. Get us certificates. In this example, we are going to be using Cloudflare.
- --certificatesresolvers.cloudflareresolver.acme.email=<[email protected]>
- --certificatesresolvers.cloudflareresolver.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.cloudflareresolver.acme.storage=./acme.json
warning
Let’s Encrypt have set limits on how many certificates you can request per certain amount of time. To test your certificate request and renewal processes, use their staging infrastructure. It is made for such purpose.
Then we mount it, for persistence.
- "./traefik/acme.json:/acme.json"
Let’s not forget to add our Cloudflare API credentials as environment variables for Traefik to use.
environment:
- CLOUDFLARE_EMAIL=<[email protected]>
- CLOUDFLARE_API_KEY=<your-api-key-goes-here>
Dashboard
Now let’s configure Traefik a bit more with a bit of labeling.
First, we specify the host Traefik should listen for to service the dashboard.
labels:
- "traefik.http.routers.dashboard-api.rule=Host(`dashboard.your-host.here`)"
- "traefik.http.routers.dashboard-api.service=api@internal"
With a little bit of Traefik documentation searching and a lot of help from htpasswd
, we can create a basicauth
login to protect the dashboard from public use.
- "traefik.http.routers.dashboard-api.middlewares=dashboard-auth-user"
- "traefik.http.middlewares.dashboard-auth-user.basicauth.users=<user>:$$pws5$$rWsEfeUw9$$uV45uwsGeaPbu8RSexB9/"
- "traefik.http.routers.dashboard-api.tls.certresolver=cloudflareresolver"
Middleware
I’m not going to go into details about the middleware flags configured here but you’re welcome to check the Traefik middleware docs.
- "traefik.http.middlewares.frame-deny.headers.framedeny=true"
- "traefik.http.middlewares.browser-xss-filter.headers.browserxssfilter=true"
- "traefik.http.middlewares.ssl-redirect.headers.sslredirect=true"
Full Configuration
Let’s put everything together now.
traefik:
container_name: traefik
image: "traefik:latest"
restart: unless-stopped
mem_limit: 40m
mem_reservation: 25m
ports:
- "80:80"
- "443:443"
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --providers.docker
- --log.level=INFO
- --api.dashboard=true
- --metrics.prometheus=true
- --certificatesresolvers.cloudflareresolver.acme.email=<[email protected]>
- --certificatesresolvers.cloudflareresolver.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.cloudflareresolver.acme.storage=./acme.json
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik/acme.json:/acme.json"
environment:
- CLOUDFLARE_EMAIL=<[email protected]>
- CLOUDFLARE_API_KEY=<your-api-key-goes-here>
labels:
- "traefik.http.routers.dashboard-api.rule=Host(`dashboard.your-host.here`)"
- "traefik.http.routers.dashboard-api.service=api@internal"
- "traefik.http.routers.dashboard-api.middlewares=dashboard-auth-user"
- "traefik.http.middlewares.dashboard-auth-user.basicauth.users=<user>:$$pws5$$rWsEfeUw9$$uV45uwsGeaPbu8RSexB9/"
- "traefik.http.routers.dashboard-api.tls.certresolver=cloudflareresolver"
- "traefik.http.middlewares.frame-deny.headers.framedeny=true"
- "traefik.http.middlewares.browser-xss-filter.headers.browserxssfilter=true"
- "traefik.http.middlewares.ssl-redirect.headers.sslredirect=true"
nginx
nginx pronounced
[engine x] is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP/UDP proxy server, originally written by Igor Sysoev.
In this example, we’re going to assume you have a static blog generated by a static blog generator of your choice and you would like to serve it for people to read it.
So let’s do this quickly as there isn’t much to tell except when it comes to labels.
nginx:
container_name: nginx
image: nginxinc/nginx-unprivileged:alpine
restart: unless-stopped
mem_limit: 8m
command: ["nginx", "-enable-prometheus-metrics", "-g", "daemon off;"]
volumes:
- "./blog/:/usr/share/nginx/html/blog:ro"
- "./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro"
environment:
- NGINX_BLOG_PORT=80
- NGINX_BLOG_HOST=<blog.your-host.here>
We are mounting the blog directory from our host to /usr/share/nginx/html/blog
as read-only into the nginx container. We are also providing nginx with a template configuration and passing the variables as environment variables as you noticed. It is also mounted as read-only. The configuration template looks like the following, if you’re wondering.
server {
listen ${NGINX_BLOG_PORT};
server_name localhost;
root /usr/share/nginx/html/${NGINX_BLOG_HOST};
location / {
index index.html;
try_files $uri $uri/ =404;
}
}
Traefik configuration
So, Traefik configuration at this point is a little bit tricky for the first time.
First, we configure the host like we did before.
labels:
- "traefik.http.routers.blog-http.rule=Host(`blog.your-host.here`)"
We tell Traefik about our service and the port to loadbalance on.
- "traefik.http.routers.blog-http.service=blog-http"
- "traefik.http.services.blog-http.loadbalancer.server.port=80"
We configure the middleware to use configuration defined in the Traefik middleware configuration section.
- "traefik.http.routers.blog-http.middlewares=blog-main"
- "traefik.http.middlewares.blog-main.chain.middlewares=frame-deny,browser-xss-filter,ssl-redirect"
Finally, we tell it about our resolver to generate an SSL Certificate.
- "traefik.http.routers.blog-http.tls.certresolver=cloudflareresolver"
Full Configuration
Let’s put the nginx service together.
nginx:
container_name: nginx
image: nginxinc/nginx-unprivileged:alpine
restart: unless-stopped
mem_limit: 8m
command: ["nginx", "-enable-prometheus-metrics", "-g", "daemon off;"]
volumes:
- "./blog/:/usr/share/nginx/html/blog:ro"
- "./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro"
environment:
- NGINX_BLOG_PORT=80
- NGINX_BLOG_HOST=<blog.your-host.here>
labels:
- "traefik.http.routers.blog-http.rule=Host(`blog.your-host.here`)"
- "traefik.http.routers.blog-http.service=blog-http"
- "traefik.http.services.blog-http.loadbalancer.server.port=80"
- "traefik.http.routers.blog-http.middlewares=blog-main"
- "traefik.http.middlewares.blog-main.chain.middlewares=frame-deny,browser-xss-filter,ssl-redirect"
- "traefik.http.routers.blog-http.tls.certresolver=cloudflareresolver"
Finale
It’s finally time to put everything together !
---
version: '2.3'
services:
traefik:
container_name: traefik
image: "traefik:latest"
restart: unless-stopped
mem_limit: 40m
mem_reservation: 25m
ports:
- "80:80"
- "443:443"
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --providers.docker
- --log.level=INFO
- --api.dashboard=true
- --metrics.prometheus=true
- --certificatesresolvers.cloudflareresolver.acme.email=<[email protected]>
- --certificatesresolvers.cloudflareresolver.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.cloudflareresolver.acme.storage=./acme.json
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik/acme.json:/acme.json"
environment:
- CLOUDFLARE_EMAIL=<[email protected]>
- CLOUDFLARE_API_KEY=<your-api-key-goes-here>
labels:
- "traefik.http.routers.dashboard-api.rule=Host(`dashboard.your-host.here`)"
- "traefik.http.routers.dashboard-api.service=api@internal"
- "traefik.http.routers.dashboard-api.middlewares=dashboard-auth-user"
- "traefik.http.middlewares.dashboard-auth-user.basicauth.users=<user>:$$pws5$$rWsEfeUw9$$uV45uwsGeaPbu8RSexB9/"
- "traefik.http.routers.dashboard-api.tls.certresolver=cloudflareresolver"
- "traefik.http.middlewares.frame-deny.headers.framedeny=true"
- "traefik.http.middlewares.browser-xss-filter.headers.browserxssfilter=true"
- "traefik.http.middlewares.ssl-redirect.headers.sslredirect=true"
nginx:
container_name: nginx
image: nginxinc/nginx-unprivileged:alpine
restart: unless-stopped
mem_limit: 8m
command: ["nginx", "-enable-prometheus-metrics", "-g", "daemon off;"]
volumes:
- "./blog/:/usr/share/nginx/html/blog:ro"
- "./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro"
environment:
- NGINX_BLOG_PORT=80
- NGINX_BLOG_HOST=<blog.your-host.here>
labels:
- "traefik.http.routers.blog-http.rule=Host(`blog.your-host.here`)"
- "traefik.http.routers.blog-http.service=blog-http"
- "traefik.http.services.blog-http.loadbalancer.server.port=80"
- "traefik.http.routers.blog-http.middlewares=blog-main"
- "traefik.http.middlewares.blog-main.chain.middlewares=frame-deny,browser-xss-filter,ssl-redirect"
- "traefik.http.routers.blog-http.tls.certresolver=cloudflareresolver"
Now we’re all set to save it in a docker-compose.yaml
file and
docker-compose up -d
If everything is configured correctly, your blog should pop-up momentarily. Enjoy !