Traefik is a feature rich, open source reverse proxy. It offers many interesting features like auto service discovery, middlewares, automatic https using let's encrypt...

For local testing purposes, we're gonna be using Vagrant + DuckDNS to simulate a server and domain to deploy traefik to. If you already have a dedicated server and domain, you can skip directly to the Bringing up Traefik section.

Table of Contents

  1. Setting up Vagrant and port forwarding
  2. Setting up DuckDNS
  3. Bringing up Traefik
  4. Setting up LetsEncrypt
  5. Setting up automatic HTTP to HTTPS redirect
  6. Deploying a docker container behind traefik
  7. Extra credit: Custom 404 page

Setting up Vagrant and port forwarding

If you don't already have Vagrant installed, you can follow their install guide.

We're gonna be using the following Vagrantfile to set up our VM:

Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/bionic64"

  # Install Docker using the corresponding vagrant provisioner
  config.vm.provision :docker

  # Forward port 80 for HTTP access
  config.vm.network :forwarded_port, guest: 80, host: 80

  # Forward port 443 for HTTPS access
  config.vm.network :forwarded_port, guest: 443, host: 443
end

Save this code to a file called Vagrantfile in a directory of your choice, then
run this command in said directory to bring up our VM:

vagrant up

Next we'll need to install docker compose, you can do so by following their install guide.

Next, you're gonna need to set up port forwarding on your router so that your vagrant box can be accessible from your public IP. Look up how to do that for your specific router.

Setting up DuckDNS

Duck DNS is a cool little tool that gives free dynamic DNS with wildcard subdomain support. We'll use it as a domain for our vagrant box.

Setting it up should easy enough, sign up on their website and follow through the instruction to get your free domain and set up the cron job resposnible for updating your ip address.

Bringing up Traefik

We need to create a traefik.toml file in the /opt/traefik/config directory with the following contents:

[accessLog]
  filePath = "/etc/traefik/access.log"
  bufferingSize = 100

[api]
  dashboard = true

[entrypoints]
  [entrypoints.web]
    address = ":80"
  [entrypoints.websecure]
    address = ":443"

[providers.docker]
  exposedbydefault = false
  network = "traefik"

[providers.file]
  directory = "/etc/traefik"
  watch = true

Quick overview of the config file:

[accessLog]
  filePath = "/etc/traefik/access.log"
  bufferingSize = 100

Since traefik writes logs asynchronously, bufferingSize represents the number of log lines Traefik will keep in memory before writing them to the selected output.

[api]
  dashboard = true

dasboard = true enables the traefik dashboard

[entrypoints]
  [entrypoints.web]
    address = ":80"
  [entrypoints.websecure]
    address = ":443"

EntryPoints are the network entry points into Traefik. They define the port which will receive the packets. Here we defined one called web for HTTP requests and websecure for HTTPS requests.

[providers.docker]
  exposedbydefault = false
  network = "traefik"

exposedbydefault = false is specified so that traefik doesn't try to expose all docker containers unless explicitly specified. Can be quite useful for not exposing unnecessary containers like databases, service workers...

network = "traefik" Defines a default docker network to use for connections to all containers, useful for when a container is connected to multiple networks.

[providers.file]
  directory = "/etc/traefik"
  watch = true

The watch option is set to true to allow Traefik to automatically watch for file changes and apply them without restarting the traefik service.

Next, we'll create a dashboard.toml file in the /opt/traefik/config/routers directory with the following contents:

[provider.file]
watch = true

[http.routers]
  [http.routers.dashboard]
    rule = "Host(`dashboard.<your-duckdns-domain>`)"
    service = "api@internal"

Create a docker-compose.yml file in the /opt/traefik/ directory with the following contents:

version: '3'

services:
  traefik:
    image: traefik:v2.2.1
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/traefik/config:/etc/traefik
    environment:
      - DUCKDNS_TOKEN=<YOUR-DUCKDNS-TOKEN>
    networks:
      - traefik
networks:
  traefik:
    external: true

Make sure you substitute <YOUR-DUCKDNS-TOKEN> with your duckdns generated token.

Before we bring up our traefik service, we need to. first create the aforementioned network that our containers are gonna connect to in order to be discovered by traefik.

We can do that by running this command:

docker network create traefik

Then we can bring up our traefik service using this command:

docker-compose up -d

If navigate in your browser to dashboard.<your-duckdns-domain> you should see the traefik dashboard in all of its glory:

Traefik Dashboard

Setting up LetsEncrypt

Open up the traefik.toml file that we created earlier and add the letsencrypt config at the end of the file:

[certificatesresolvers.letsEncrypt.acme]
  email = "<your-email>"
  storage = "/acme.json"
  [certificatesresolvers.letsEncrypt.acme.dnschallenge]
    provider = "duckdns"
    delayBeforeCheck = 0

For the sake of this tutorial, we are using the 'duckdns' provider for our dns challenge, but traefik supports many many more providers. You check out the full list here:

https://docs.traefik.io/https/acme/

Setting up automatic HTTP to HTTPS redirect

Create a file called redirect.toml in the /opt/traefik/config/middlewares directory, with the following contents:

[provider.file]
watch = true

[http.middlewares.redirect.redirectscheme]
  scheme = "https"

[http.routers]
  [http.routers.http-to-https]
    entryPoints = ["web"]
    middlewares = ["redirect"]
    rule = "HostRegexp(`{host:.+}`)"
    service = "dummy"

[http.services]
  [http.services.dummy.loadBalancer]
    [[http.services.dummy.loadBalancer.servers]]
      url = ""

Let's see what's going in this file.

[http.middlewares.redirect.redirectscheme]
  scheme = "https"

Here, we're creating a middleware called redirect that uses traefik's redirectscheme to redirect all of a router's http requests to https.

Since traefik doesn't support global middlewares that modify all incoming requests and instead does it on a per router basis. In the second snippet:

[http.routers]
  [http.routers.http-to-https]
    entryPoints = ["web"]
    middlewares = ["redirect"]
    rule = "HostRegexp(`{host:.+}`)"
    service = "dummy"

[http.services]
  [http.services.dummy.loadBalancer]
    [[http.services.dummy.loadBalancer.servers]]
      url = ""

We're creating a dummy catch-all router that uses the redirect middleware to redirect incoming requests to HTTPS. For this router, we're specifying the entryPoints = ["web"] option to only catch http requests and not fall into an endless loop of redirects.

A full list of traefik's middlewares can be found here:

https://docs.traefik.io/middlewares/overview/

Now we're gonna modify the dashboard.toml file so that it only accepts HTTPS requests and use our letsencrypt certificate resolver:

[provider.file]
watch = true

[http.routers]
  [http.routers.dashboard]
    entrypoints = ["websecure"] # Only accept HTTP requests
    rule = "Host(`dashboard.<your-duckdns-domain>`)"
    service = "api@internal"
    [http.routers.dashboard.tls]
      certresolver = "letsEncrypt" # Use our letsencrypt certificate resolver

If you now go to dashboard.<your-duckdns-domain> on your browser, it should redirect you to https and you should have a valid ssl certificate.

Deploying a docker container behind traefik

Let's now see how you can deploy a container behind traefik. We're gonna be using the nginxdemos/hello image for this example.

Let's create a docker compose file that uses this image, and see how we can make the container visible to traefik.

version: "3"

services:
  nginx-demo:
    image: nginxdemos/hello
    container_name: traefik-demo
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-demo.entrypoints=websecure"
      - "traefik.http.routers.traefik-demo.rule=Host(`traefik-demo.<your-duckdns-domain>`)"
      - "traefik.http.routers.traefik-demo.tls=true"
      - "traefik.http.routers.traefik-demo.tls.certresolver=letsEncrypt"
      - "traefik.http.services.traefik-demo.loadbalancer.server.port=80"
    networks:
      - traefik

networks:
  traefik:
    external: true

Traefik uses docker labels to exppose containers.
traefik.enable=true We specify that we want to expose this container through traefik.

traefik.http.routers.traefik-demo.entrypoints We only want to accept HTTPS requests.

traefik.http.routers.traefik-demo.rule We set the url we want to access the container from.

traefik.http.routers.traefik-demo.tls Enable tls.

traefik.http.routers.traefik-demo.tls.certresolver We specify the certificate resolver we want to use.

traefik.http.services.traefik-demo.loadbalancer.server.port The port that traefik should use to access the container. Since traefik automatically detects exposed ports, this value can be omitted if your container only exposes one port. But if it exposes multiple ports this value should be specied or else traefik is going to use the first port that docker returns.

    networks:
      - traefik

We add the container to the traefik network.

Now if you access traefik-demo.<your-duckdns-domain> you should see this page:

Congratulations, you're now successfully running traefik on you machine!

Extra credit: Custom 404 page

A cool thing we could do is change the boring default 404 page that traefik has.

This is the default page:

Screenshot-2020-05-08-at-13.56.45

We're gonna be changing it with this animation from codepen:

yeti-404

You can either export the codepen and create your own docker image or use the docker compose file below:

version: "3"

services:
  global-404:
    image: ttlgeek/yeti-404
    container_name: global-404
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.global-404.entrypoints=websecure"
      - "traefik.http.routers.global-404.rule=HostRegexp(`{host:.*}`)"
      - "traefik.http.routers.global-404.tls=true"
      - "traefik.http.routers.global-404.tls.certresolver=letsEncrypt"
      - "traefik.http.services.global-404.loadbalancer.server.port=80"
networks:
  traefik:
    external: true