Traefik v2 for Single Host Setups

Sören Klein
7 min readAug 8, 2023

This tutorial will set up Traefik on a Docker host. Through the use of a Docker network, multiple Docker Compose files can use the single Traefik instance. Some common pitfalls are explained among the way.

Note 1: This tutorial is only for Traefik v2. As of the time of this writing, Traefik v3 still needs to be released. There will be a follow-up article for v3 within the year.

Note 2: Traefik v2 can be configured in multiple ways; via a dedicated YML file, a dedicated TOML file, or Docker labels. While the last variant does not provide all configuration options, it is more straightforward and sufficient for most setups and therefore used in this tutorial.

Requirements

The first step is to install Docker. Digital Ocean provides great tutorials on installing Docker on a new system: How to Install and Use Docker.

Check that Docker is successfully installed by querying the versions:

$ docker --version
Docker version 24.0.5, build ced0996

$ docker compose version
Docker Compose version v2.20.2

Next, make sure that the server is already accessible via a domain. This, in turn, requires a static IP address (easy) or a dynamic DNS setup, which can get tricky.

The server should be able to communicate over IPv4. Using only IPv6 is technically possible but requires non-standard Docker setups.

Setting up Traefik

Traefik network

First of all, we create a new Docker network on the host, which multiple Docker Compose setups can later use:

$ docker network create traefik
97cc41e51449338b808a2102b1c28972ae8b603c65d44f771d9d952dc522b58e

If you are running RHEL or similar distros, you will likely need to open up the firewall to allow internal docker networks:

$ sudo firewall-cmd --zone=public --add-masquerade --permanent
$ sudo firewall-cmd --reload

Traefik assets

Our Traefik configuration will be saved at the location /srv/traefik and will contain the following content:

  • docker-compose.yml: Docker configuration file containing the Traefik and Traefik cert dumper containers.
  • .env: Contains variables for your specific setup, e.g. domain name and HTTP basic auth credentials for the Traefik dashboard.
  • acme.json: The file where Traefik stores all your HTTPS certificates.
  • certs: A folder containing the aforementioned certificates in a more common format.

First of all, create the required /srv/traefik directory and check it out:

$ sudo mkdir /srv/traefik
$ cd /srv/traefik

Then create the docker configuration by typing sudo nano docker-compose.yml, pasting the following text into the editor, save it with control key + O, and close the file with control key + X:

version: '3.8'

services:

traefik:
image: 'traefik:v2.10'
container_name: traefik
restart: unless-stopped
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --providers.docker
- --providers.docker.exposedByDefault=false
- --providers.docker.network=traefik
- --api
- --certificatesresolvers.le.acme.caserver=$ACME_SERVER
- --certificatesresolvers.le.acme.email=$ACME_EMAIL
- --certificatesresolvers.le.acme.storage=/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
ports:
- '80:80'
- '443:443'
volumes:
- '/var/run/docker.sock:/var/run/docker.sock:ro'
- './acme.json:/acme.json'
networks:
- traefik
labels:
- 'traefik.enable=true'

# dashboard
- 'traefik.http.routers.traefik.rule=Host(`$DOMAIN_TRAEFIK`)'
- 'traefik.http.routers.traefik.service=api@internal'
- 'traefik.http.routers.traefik.tls.certresolver=le'
- 'traefik.http.routers.traefik.entrypoints=websecure'
- 'traefik.http.routers.traefik.middlewares=authtraefik'
- 'traefik.http.middlewares.authtraefik.basicauth.users=$TRAEFIK_HTTP_BASIC_AUTH'

# global redirect to https
- 'traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)'
- 'traefik.http.routers.http-catchall.entrypoints=web'
- 'traefik.http.routers.http-catchall.middlewares=redirect-to-https'

# middleware redirect
- 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'

certdumper:
image: ldez/traefik-certs-dumper:latest
container_name: traefik_certdumper
restart: unless-stopped
command: 'file --watch --domain-subdir=true --version v2'
volumes:
- './acme.json:/acme.json:ro'
- './certs:/dump'


networks:
traefik:
name: traefik
external: true

Create an empty .env file, which will get filled later on with your host-specific configuration:

$ sudo touch .env

Now create the last file: sudo nano acme.json:

{}

Finally, create the certs directory and make sure that all permissions are correct:

$ sudo mkdir certs
$ sudo chmod 600 acme.json

Configuring Traefik

The Traefik configuration expects four environment variables:

  • ACME_SERVER: The URL to Let’s Encrypts ACME challenge. Two values are possible: The production endpoint for issuing real certificates, https://acme-v02.api.letsencrypt.org/directory, and the staging endpoint for testing your setup, https://acme-staging-v02.api.letsencrypt.org/directory.
  • ACME_EMAIL: An email address that will get notified by Let’s Encrypt if there is any problem with the certificate renewal.
  • DOMAIN_TRAEFIK: The domain under which Traefik’s dashboard is available, usually traefik.your-domain.example. Requires a valid domain and corresponding A / AAAA DNS entries.
  • TRAEFIK_HTTP_BASIC_AUTH: The HTTP basic auth credentials for accessing your Traefik dashboard. You can generate a valid username-password-pair with the following command: echo $(htpasswd -nB "username") | sed -e s/\\$/\\$\\$/g. Note: Single dollar signs will be substituted as variables, while two dollar signs get interpreted as a single dollar sign character. The command already takes care of that.

Edit the file via sudo nano .env. The final .env file should look like this:

ACME_SERVER=https://acme-v02.api.letsencrypt.org/directory
ACME_EMAIL=info@your-domain.example
DOMAIN_TRAEFIK=traefik.your-domain.example
TRAEFIK_HTTP_BASIC_AUTH=username:$$algorithm$$settings$$hash

Checking the configuration

To check that the docker configuration is correct and free of syntax problems, you can execute the following command:

$ docker compose config

If there are no problems, the finished docker-compose.yml file will be printed.

Possible error messages are:

parsing /srv/traefik/docker-compose.yml: yaml: line 23: did not find expected ‘-’ indicator

In this case, the YML syntax has a problem. Paste the content of your docker-compose.yml file into a YML syntax checker, e.g. https://www.yamllint.com/. Missing quotes or bad indentation are common problems.

WARN[0000] The “H6YgTtZ051jFhvwJMvaxFZVw6D” variable is not set. Defaulting to a blank string.

The value of TRAEFIK_HTTP_BASIC_AUTH contains single dollar characters, $. Replace them with two dollar characters: $ -> $$.

Starting Traefik

Start Traefik by executing the following command:

$ docker compose up
[+] Running 9/9
✔ traefik 4 layers [⣿⣿⣿⣿] 0B/0B Pulled 3.9s
✔ 31e352740f53 Pull complete 0.9s
✔ 4b9a9b499d7a Pull complete 1.2s
✔ 3ed0f8d1a106 Pull complete 2.0s
✔ a842830f041a Pull complete 2.0s
✔ certdumper 3 layers [⣿⣿⣿] 0B/0B Pulled 3.8s
✔ 59bf1c3509f3 Pull complete 1.5s
✔ 921016227070 Pull complete 1.6s
✔ 8d8bc8dbf8db Pull complete 1.9s
[+] Running 3/3
✔ Network traefik_default Created 0.2s
✔ Container traefik Created 0.1s
✔ Container traefik_certdumper Created 0.1s
Attaching to traefik, traefik_certdumper
traefik | time="2023-08-07T17:26:41Z" level=info msg="Configuration loaded from flags."

Then visit the domain for your traefik endpoint, i.e. traefik.your-domain.example. Initially, Traefik will use a temporary certificate, which will get replaced by a real one from Let’s Encrypt soon. If there is any problem getting the certificate, Traefik will post it in the docker logs.

Note: If your domain or top level domain is on the HSTS list, the first unsecured certificate will be denied by modern browsers. Other tools like curl or wget do not care though. Let’s Encrypt still requires port 80 to be opened.

The Traefik dashboard after the first startup.
Firefox’s error page for accessing HSTS protected domains with no valid certificate.

Starting other containers

To start up other containers and proxy web requests through docker, you need to create a docker compose file similar to this one:

version: '3.8'

services:

hello-world:
image: nginxdemos/hello
container_name: hello-world
labels:
- 'traefik.enable=true'
- 'traefik.http.services.hello-world.loadbalancer.server.port=80'
- 'traefik.http.routers.hello-world.rule=Host(`your-domain.example`)'
- 'traefik.http.routers.hello-world.entrypoints=websecure'
- 'traefik.http.routers.hello-world.tls=true'
- 'traefik.http.routers.hello-world.tls.certresolver=le'

networks:
traefik:
name: traefik
external: true

Instance specific parts are:

  • Replace the domain name your-domain.example with your domain.
  • If your image uses another port than 80, change it.
  • Replace hello-world on the left side of the labels with the name of your docker container. This name must be unique on your machine; starting several containers with the same label keys will result in only one available container.

Start your new service:

$ docker compose up -d
[+] Running 12/12
✔ hello-world 11 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿] 0B/0B Pulled 4.3s
✔ 4db1b89c0bd1 Pull complete 0.7s
✔ bd338968799f Pull complete 0.9s
✔ 6a107772494d Pull complete 0.9s
✔ 9f05b0cc5f6e Pull complete 1.0s
✔ 4c5efdb87c4a Pull complete 1.1s
✔ c8794a7158bf Pull complete 1.1s
✔ 8de2a93581dc Pull complete 1.6s
✔ 768e67c521a9 Pull complete 2.1s
✔ 3ae918acfca0 Pull complete 2.1s
✔ 6f5621165b8f Pull complete 2.4s
✔ 1774fe043af2 Pull complete 2.4s
[+] Running 2/2
✔ Network hello_default Created 0.4s
✔ Container hello-world Created 0.1s
Attaching to hello-world
hello-world | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
hello-world | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
hello-world | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
hello-world | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
hello-world | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
hello-world | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
hello-world | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
hello-world | /docker-entrypoint.sh: Configuration complete; ready for start up
hello-world | 2023/08/07 17:44:40 [notice] 1#1: using the "epoll" event method
hello-world | 2023/08/07 17:44:40 [notice] 1#1: nginx/1.25.1
hello-world | 2023/08/07 17:44:40 [notice] 1#1: built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4)
hello-world | 2023/08/07 17:44:40 [notice] 1#1: OS: Linux 5.14.0-284.18.1.el9_2.x86_64
hello-world | 2023/08/07 17:44:40 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
hello-world | 2023/08/07 17:44:40 [notice] 1#1: start worker processes
hello-world | 2023/08/07 17:44:40 [notice] 1#1: start worker process 19
hello-world | 2023/08/07 17:44:40 [notice] 1#1: start worker process 20
hello-world | 2023/08/07 17:44:40 [notice] 1#1: start worker process 21
Hello world from NGINX, through Traefik.

If Traefik returns 404, then it is likely that your firewall blocks the communication between Traefik and the other Docker container. See the earlier chapter “Traefik network” for more details.

--

--