Frank W

Frank logo

Traefik 2.0 upgrade with Docker Compose

By Frank W on

So apparently I wasn’t the only one who woke up one day to find everything offline because Traefik 2 had been released with breaking changes because I was running Watchtower and using the Traefik:latest tag.

I was also not the only one to quickly try and fix the issue, but Traefik 2 was quite a significant change, and the issue was not going to solved in a few mins. It was rollback time for many people, including myself, particularly with the lack of decent documentation available at the time.

Anyway, yesterday I was quite sick and at home, and decided to attempt this upgrade. I mean, how hard can it be? Not that hard to be honest, I was done in about 3hrs.

Here’s what you need to do in 2 simple steps

  1. Run the traefik-migration-tool to convert your Letsencrypt ACME config and your static config to 2.0 format.
  2. Update your docker compose file based on the new Traefik architecture (routers, middleware and services)

I won’t go over step 1, as this is very simple and the tool provides enough help – short version is you just run the tool against the acme.json file and traefik.toml file to get new v2 compatible versions. I had some warnings about SSL redirects, which were previous defined in the entrypoint but are now handled by middleware.

Step 2, however, is where things get a little tricker due to the new architecture of Traefik. There is a v1 to v2 migration guide, which helped a lot, and a blog post on version 2, but there were still things that were not well explained in the guide, which is what I’d like to highlight here.

New Traefik Architecture

First, lets look at a couple of examples, all of which can be found either in the current version, or previous versions, of my docker-compose.yml on Github.

Traefik Image

This is the labels section from the Traefik docker container (nothing else changed).

    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik.rule=Host(`traefik.${DOMAINNAME}`)
      - traefik.http.routers.traefik.entrypoints=https
      - traefik.http.routers.traefik.tls=true
      - traefik.http.routers.traefik.middlewares=auth
      - traefik.http.routers.traefik.service=api@internal
      - traefik.http.services.traefik.loadbalancer.server.port=8080
      - traefik.http.middlewares.auth.basicauth.users=${HTTP_USERNAME}:${HTTP_PASSWORD}
      - traefik.http.middlewares.sslredirect.redirectscheme.scheme=https
      - traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)
      - traefik.http.routers.http-catchall.entrypoints=http
      - traefik.http.routers.http-catchall.middlewares=sslredirect

Let’s break this down section by section.

      - traefik.enable=true
      - traefik.http.routers.traefik.rule=Host(`traefik.${DOMAINNAME}`)
      - traefik.http.routers.traefik.entrypoints=https
      - traefik.http.routers.traefik.tls=true
      - traefik.http.routers.traefik.middlewares=auth
      - traefik.http.routers.traefik.service=api@internal

Here, I am enabling traefik to route traffic, because I have “exposedByDefault = false” set in my traefik.toml. I am then defining a routers, with a rule to match my hostname, the entry points, and enabling TLS.

I’m also telling the router to pass traffic through an authentication middleware (defined below), and to send traffic to Traefik’s own API (expose the Web UI).

      - traefik.http.services.traefik.loadbalancer.server.port=8080
      - traefik.http.middlewares.auth.basicauth.users=${HTTP_USERNAME}:${HTTP_PASSWORD}

The first line above is how you now define the port to send traffic to, which stumped me for a while.

Here I am also defining a new, reusable middleware that I can use with any router to password protect any application or container I have running.

      - traefik.http.middlewares.sslredirect.redirectscheme.scheme=https
      - traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)
      - traefik.http.routers.http-catchall.entrypoints=http
      - traefik.http.routers.http-catchall.middlewares=sslredirect

Lastly, we are defining a second middleware for redirecting non-SSL traffic, named “sslredirect” by giving it a scheme of https. To wrap up, we are defining a default catch-all non-SSL redirect, which can be accomplished in 3 lines.

Now, we have Traefik up and running with an accessible Web UI. Now all we need to do now is set Traefik up to pass traffic through to other containers you may have running.

I’ll use Transmission as an example of one container I have running, which Traefik passes HTTPS traffic to.

  transmission:
    image: linuxserver/transmission
    container_name: transmission
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - TRANSMISSION_WEB_HOME=/transmission-web-control/
    volumes:
      - ${USERDIR}/docker/transmission:/config
      - ${USERDIR}/TransmissionDL:/downloads
    ports:
      - 55700:55700
      - 9091:9091
      - 55700:55700/udp
    restart: unless-stopped
    networks:
      - traefik_proxy
      - default
    labels:
      - traefik.enable=true
      - traefik.http.routers.transmission.rule=Host(`transmission.${DOMAINNAME}`)
      - traefik.http.routers.transmission.entrypoints=https
      - traefik.http.routers.transmission.tls=true
      - traefik.http.services.transmission.loadbalancer.server.port=9091
      - traefik.docker.network=traefik_proxy

In this example, the only section that changed between Traefik 1.7 and 2.0 is the labels section. Hopefully, everything is self-explanatory after the above explanation. We are just defining two routers, one with an sslredirect middleware, and setting the port on the router depending on your container.

That’s it, rinse and repeat, or look at my docker-compose.yml for more examples.

Comments

JustinW

JustinW

Are you able to provide a copy of your .toml as well. This is great and simplifying the migration.

Frank W

Frank W

Hi Justin,

traefik.toml is as follows:

[global]
  checkNewVersion = true
  sendAnonymousUsage = true

[serversTransport]
  insecureSkipVerify = true
  maxIdleConnsPerHost = 0

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

[providers]
  providersThrottleDuration = "2s"
  [providers.docker]
    watch = true
    endpoint = "unix:///var/run/docker.sock"
    swarmModeRefreshSeconds = "15s"
    exposedByDefault = false
  [providers.file]
    watch = true
    filename = "/etc/traefik/rules.toml"

[api]
  dashboard = true

[log]
  level = "INFO"

[certificatesResolvers]
  [certificatesResolvers.default]
    [certificatesResolvers.default.acme]
      email = "my email"
      storage = "/etc/traefik/acme/acme-new.json"
      [certificatesResolvers.default.acme.dnsChallenge]
        provider = "cloudflare"
        delayBeforeCheck = "1m0s"

rules.toml:

[tls.options]
  [tls.options.default]
    minVersion = "VersionTLS12"
    sniStrict = true
    cipherSuites = [
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", # TLS 1.2
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", # TLS 1.2
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
    "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_FALLBACK_SCSV"
    ]

I have also recently added the following to my docker-compose file to get A+ status on SSL Labs test, and applied this middleware to various routers:

      - traefik.http.middlewares.securityheaders.headers.forceSTSHeader=true
      - traefik.http.middlewares.securityheaders.headers.stsPreload=true
      - traefik.http.middlewares.securityheaders.headers.stsSeconds=315360000
      - traefik.http.middlewares.securityheaders.headers.stsIncludeSubdomains=true

Leave a Comment