Traefik v2.0 introduces a series of breaking changes as they have refactored much of their internal code as well as replaced old concepts with new ones. In general, the old notion of frontends and backends have been replaced with routers, middlewares, and services. We will take a quick look at what this means in practice before diving into how the new configuration format looks.

First of all, the responsibility of a frontend in version 1 now falls to what is called a router in version 2. The router is responsible for defining the ruleset to perform request matching on, as well as any potential middleware that should be applied.

Previously, it was the backend that performed any modifications to requests before sending it to the underlying service. However, this is now deferred to a new component called a middleware. So for instance the act of enabling HTTPS redirect on a service is now handled by a middleware. The same applies for protecting a service with BasicAuth.

Fore a more detailed look at what it looks like, head over to the official Traefik docs which have a very good level of detail, as well as configuration examples in both different file formats and for different providers.

Structural changes to the configuration file

Previously, you could define as much config as you wanted in traefik.toml or as labels on the docker container. However, the configuration has now been categorically split into two domains, static and dynamic configuration.

  • static configuration is read once on startup, and lives in traefik.toml as previously
  • dynamic configuration can come from any provider, is dynamically reloaded and is expected to change at any time.

A very important thing to notice here is that this entails that you cannot define your previous frontends and backends in traefik.toml, even when setting up access to the dashboard.

This means that for what we are going to set up in this guide, the traefik.toml file becomes quite slim. Here is a complete example from my own setup, which uses Let's Encrypt to obtain TLS certificates for all endpoints, as well as integrate with the Docker daemon for auto-detecting and potentially exposing new containers.

# General

[log]
  level = "WARNING"

# Entrypoints

[entryPoints.http]
  address = ":80"
[entryPoints.https]
  address = ":443"

# Dashboard

[api]
  dashboard = true

# Dynamic configuration providers

[providers.file]
  directory = "/conf.d"
[providers.docker]
  endpoint = "unix:///var/run/docker.sock"
  network = "traefik"
  exposedByDefault = false

# Certificate resolvers

[certificatesResolvers.le.acme]
  email = "me@domain.org"
  storage = "acme.json"
  [certificatesResolvers.le.acme.tlsChallenge]

The le part of the certificatesResolvers namespace is a name I chose for myself, and you can call it what you like.

Next, we take a look at the dynamic part of the configuration. Since we will be running Traefik in a Docker container, the configuration part will be in the form of container labels. Check out the official docs for alternate formats like Toml if you'd rather use the file configuration provider. Below is a complete example from the Traefik docker-compose.yml file of the setup for this site.

services:
  traefik:
    image: traefik:v2.1.6
    ports:
      - 80:80
      - 443:443
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`dashboard-subdomain.overflow.no`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=le"
      - "traefik.http.routers.dashboard.middlewares=dashboardAuth"
      - "traefik.http.middlewares.dashboardAuth.basicauth.users=user:$$xx$$xx$$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      - "traefik.http.routers.httpCatchall.rule=HostRegexp(`{any:.+}`)"
      - "traefik.http.routers.httpCatchall.entrypoints=http"
      - "traefik.http.routers.httpCatchall.middlewares=httpsRedirect"
      - "traefik.http.middlewares.httpsRedirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.httpsRedirect.redirectscheme.permanent=true"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/path/to/traefik/traefik.toml:/traefik.toml"
      - "/path/to/traefik/conf.d:/conf.d"
      - "/path/to/traefik/acme.json:/acme.json"
    container_name: traefik

networks:
  traefik:
    external: true

First of all, since we have exposeByDefault set to false in our static configuration, which is a sane approach imho, we have to set traefik.enable=true like in version 1.

The host matching rule now lives under the http.routers namespace, where I have created a node called dashboard (you are free to chose the name you like here).

In order to expose the dashboard, api.dashboard must be set to true in the static configuration (traefik.toml). In addition, we must point our router to the internal api service, which is a reserved service called api@internal.

To enable TLS for our dashboard, we set tls=true (this will tell Traefik that this route should perform TLS termination) and make it use our Let's Encrypt certificate resolver we defined in static configuration, named le: tls.certresolver=le.

Next we have the new concept of a middleware. It has been set to a custom named middleware called dashboardAuth, which uses Basic Authentication. The BasicAuth config functions the same way as in version 1. Please note that since this is in a Docker Compose file, we have to add double $ in the users list, which is comma separated, in order to escape the Docker variable interpolation. To create a htpasswd string, install apache2-utils and run the command htpasswd -n -B <username>.

To enable global HTTPS redirect, the config has become a bit more ugly I feel. However it works like a charm, by defining a custom router which I have called httpCatchall, that uses regex to match any hostname for the http entrypoint, and dispatches to the httpsRedirect middleware we define in the last steps.

The HTTPS redirect middleware is named httpsRedirect, again a name of my choosing (starting to see the config pattern here?), and set the builtin property redirectscheme.scheme=https and redirectscheme.permanent=true.

Defining a service

The below example is how easy it is to expose a new service that should have TLS enabled using Let's Encrypt using Docker. Just add these labels to your docker container (i.e in docker-compose.yml).

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myService.rule=Host(`myservice.mydomain.org`)"
      - "traefik.http.routers.myService.tls=true"
      - "traefik.http.routers.myService.tls.certresolver=le"

You don't even have to define what port the container exposes as long as it only exposes one port. If it exposes many, you will have to define the port using myService.loadBalancer.servers.

If configuring using a regular file (auto-reload on save by file watching is enabled by default), here is the same configuration as above.

[http.routers]
  [http.routers.myRouter]
    rule = "Host(`myservice.domain.org`)"
    service = "myService"

    [http.routers.myService.tls]
        certResolver = "le"


[http.services]
  [http.services.myService.loadBalancer]
    [[http.services.myService.loadBalancer.servers]]
        url = "http://<ip>:<port>"

Make note of the [[ ]] double brackets for the servers node. It represents an "Array of Tables" in Toml, also called a matrix or 2D array. It allows you to specify multiple servers, enabling load balancing by round robin. See the official docs for more information.

If you wish to add middlewares to your service, like for instance as we did with BasicAuth, here is an extended example.

[http.routers]
  [http.routers.myRouter]
    rule = "Host(`myservice.domain.org`)"
    service = "myService"
    middlewares = ["myServiceAuth"]

    [http.routers.myService.tls]
        certResolver = "le"


[http.services]
  [http.services.myService.loadBalancer]
    [[http.services.myService.loadBalancer.servers]]
        url = "http://<ip>:<port>"

[http.middlewares]
  [http.middlewares.myServiceAuth.basicAuth]
    users = ["user:$xx$xx$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]

Migrating from v1 to v2

Depending on your setup, migrating from 1 to 2 can be a bit time consuming, I'll be honest. The Traefik team has developed a traefik-migration-tool that you can use to automatically migrate parts of your configuration to the new format. This includes changes to the acme.json used by the Let's Encrypt certificate resolver. To be quite honest, so much of my config was reported by the tool to require manual migration that I just ended up using the tool to migrate my acme.json from v1 to v2. Regardless it seemed my certificates were re-created, so you might as well just delete the old one and start from scratch and ignore the tool altogether. The main takeaway for me was that each line in my config that had to be manually migrated also provided a direct link to the docs covering that topic, which was nice.

Installing and configuring Traefik with Docker and Ansible

So let's finally go through how you can quickly get up and running using Traefik. Please make sure you have docker and docker-compose installed on your server before proceeding. This setup also depends on you having ansible installed on your local machine.

I've set up a small ansible role that takes care of the grunt work. You can clone it from my GitHub repository.

git clone git@github.com:myth/ansible-traefik-docker.git

Create a playbook (i.e traefik-playbook.yml):

---
- name: Install and configure Traefik reverse-proxy
  hosts: <your host group or individual host>
  roles:
    - role: ansible-traefik-docker
      traefik_domain: "mydomain.org"
      traefik_acme_email: "user@mydomain.org"
      traefik_dashboard_basicauth_users: ["user:$$apr1$$somehash"]

Remember to update your hosts, domain, acme email and basicauth users.

Then, using your inventory file, run the playbook.

ansible-playbook -i <my-inventory-file> traefik-playbook.yml

That's it, you should be up and running. You should now be able to access the dashboard at traefik.mydomain.org using your credentials.

Be aware that at first launch you will get certificate errors, as Traefik provides a self-signed default cert used when starting, so until the Let's Encrypt challenge is complete, you will have insecure connection warnings in your browser. I experienced that Chrome showed it as insecure even after the certificate was created (took about 20 seconds), but closing and reopening the tab solved that issue.

Installing and configuring Traefik with Docker without Ansible

Well, you have all you need at the beginning of this guide, the complete docker-compose.yml and traefik.toml examples are enough to set everything up. Just remember to also create a file called acme.json with 0600 in file permissions in the same directory as traefik.toml and you should be able to start by issuing docker-compose up -d.

Good luck!