Let's play with Traefik

5 minute read

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 !