Setting Up Traefik

Alexis Hevia
7 min readMay 11, 2023

--

In this post I’ll show you how to set up Traefik Proxy on a cloud compute instance.

Traefik Proxy will allow us to host multiple services under a single domain. For example, I host the following services under my personal domain:

  • Photoprism (online photo album) is exposed on https://photoprism.alexishevia.com
  • Navidrome (music player) is exposed on https://music.alexishevia.com
  • Miniflux (RSS reader) is exposed on https://miniflux.alexishevia.com

Each of those services is running as a separate docker container inside the same compute instance.

This post assumes you already have a compute instance with Docker and Docker Compose installed. If you do not have one, check out “Setting up a Compute Instance in OCI”.

Registering your own domain

The first step in your self hosting journey is to register your own domain.

Having your own domain will allow you to easily access your services, which will all be hosted in the same compute instance.

There are many companies that sell domains (search online for “domain registrars”). I personally use Cloudflare Registrar, but feel free to use whichever one best serves your needs.

Pointing your domain to your compute instance

Go into the DNS settings for your domain, and add the following records:

| Record Type | Name                | Value               | TTL  |
| --- | --- | --- | --- |
| A | <YOUR_DOMAIN.com> | <SERVER_IP_ADDRESS> | 3600 |
| A | *.<YOUR_DOMAIN.com> | <SERVER_IP_ADDRESS> | 3600 |

(Replace with your own domain name and the IP address for your compute instance)

Example:

| Record Type | Name              | Value         | TTL  |
| --- | --- | --- | --- |
| A | alexishevia.com | 143.47.51.100 | 3600 |
| A | *.alexishevia.com | 143.47.51.100 | 3600 |

Setting up Traefik

Create a new directory on your local computer, and call it selfhost . Inside selfhost , create a file called docker-compose.yml and add the following contents:

# docker-compose.yml

version: "3.8"

services:

traefik:
image: traefik:v2.10.1
restart: unless-stopped
command:
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --providers.docker=true
- --providers.docker.exposedByDefault=false # require containers to define `traefik.enable=true` to be exposed
- --api
- --certificatesresolvers.letsencryptresolver.acme.email=${EMAIL}
- --certificatesresolvers.letsencryptresolver.acme.storage=/acme.json
- --certificatesresolvers.letsencryptresolver.acme.tlschallenge=true
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro # allow Traefik to listen to Docker events
- ${TRAEFIK_DIR}/acme.json:/acme.json # stores ACME (HTTPS) certificates
labels:
- traefik.enable=true

# "admin" middleware definition
# to be used by services that do not have their own security
- traefik.http.middlewares.admin.basicauth.users=${HTTP_BASIC_USER}:${HTTP_BASIC_PWD}

# expose the traefik dashboard
- traefik.http.routers.traefik.entrypoints=websecure
- traefik.http.routers.traefik.rule=Host(`traefik.${DOMAINNAME}`)
- traefik.http.routers.traefik.service=api@internal
- traefik.http.routers.traefik.middlewares=admin
- traefik.http.routers.traefik.tls.certresolver=letsencryptresolver

Let’s break down this docker-compose.yml file.

version: "3.8"

services:

traefik:
image: traefik:v2.10.1
restart: unless-stopped

We start by declaring we’ll be using version 3.8 of docker-compose.

Then, inside the services block we define our first service — called traefik .

The traefik service will run the traefik version 2.10.1 image from docker hub.

The restart: unless-stopped line instructs docker to restart our service if it dies for any reason— unless we manually stop it. See: Use a restart policy

command:
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443

All entries under command are command line arguments we are sending to the traefik command on startup. Traefik docs refer to these command line arguments as the Static Configuration.

In these first 4 arguments we are defining two entrypoints: web and websecure . The web entrypoint handles http traffic over port 80 , while websecure handles https traffic over port 443 .

Because we want all traffic to happen over https, we are adding redirection rules to our web entrypoint. Any traffic coming over http will be redirected to https.

- --providers.docker=true
- --providers.docker.exposedByDefault=false

With these two lines we are enabling the Docker provider for Traefik, and we are instructing Traefik to not expose our containers by default. With these settings, we will be able to expose some docker containers (like navidrome and photoprism) to the Internet, and at the same time keep some docker containers (like databases) hidden from external traffic.

- --api

The --api argument enables the Traefik Dashboard. The Traefik dashboard provides a visual tool to see how our routes, middlewares, and services are behaving.

- --certificatesresolvers.letsencryptresolver.acme.email=${EMAIL}
- --certificatesresolvers.letsencryptresolver.acme.storage=/acme.json
- --certificatesresolvers.letsencryptresolver.acme.tlschallenge=true

The Let’s Encrypt resolver will automatically take care of registering and keeping our HTTPS certificates up to date. The certificates will be saved in the file /acme.json

Notice we’re using ${EMAIL} as a placeholder for our email address. We will create a .env file later, and we’ll define a value for EMAIL in that .env file. See: Ways to set environment variables in Compose

ports:
- 80:80
- 443:443

With these two rules we’re exposing ports 80 and 443 from the traefik docker container to the outside world.

volumes:
- /etc/localtime:/etc/localtime:ro

The volumes block in docker-compose.yml allows us to map files and directories from our compute instance into the docker container.

The /etc/localtime file is the local timezone configuration file. By sharing the compute instance’s /etc/localtime with the container, we make sure the container uses the same timezone as the compute instance.

- /var/run/docker.sock:/var/run/docker.sock:ro

The /var/run/docker.sock is the Docker socket. The traefik container needs access to the Docker socket in order to listen to Docker events.

- ${TRAEFIK_DIR}/acme.json:/acme.json

In our commands block, we instructed the Let’s Encrypt resolver to store HTTPS certificates on the /acme.json file. This path is relative to the Docker container. If we don’t map this path to the compute instance, the file will be lost when the container is stopped.

With this line we are mapping the container’s /acme.json path to the compute instance’s ${TRAEFIK_DIR}/acme.json path, so it persists between container restarts.

We’ll configure a valid path for ${TRAEFIK_DIR} in our .env file later.

labels:
- traefik.enable=true

Traefik uses docker-compose labels to set configuration values for each container.

In this case, we are enabling Traefik Proxy on the traefik container. This will allow us to expose the Traefik Dashboard through our Traefik Proxy.

- traefik.http.middlewares.admin.basicauth.users=${HTTP_BASIC_USER}:${HTTP_BASIC_PWD}

In this line we are defining a new middleware called admin, which will require any visitor to authenticate using Basic Auth.

We will use the admin middleware for any service that does not have its own built-in authentication.

We will define values for ${HTTP_BASIC_USER} and ${HTTP_BASIC_PWD} in our .env file

- traefik.http.routers.traefik.entrypoints=websecure
- traefik.http.routers.traefik.rule=Host(`traefik.${DOMAINNAME}`)
- traefik.http.routers.traefik.service=api@internal

In this block we are defining a new router called traefik. This router will receive any requests to https://traefik.${DOMAINNAME} (eg: https://traefik.alexishevia.com), and forward it to theapi@internal service— which is the Traefik Dashboard.

- traefik.http.routers.traefik.middlewares=admin

Since the Traefik Dashboard does not have any built-in authentication, we are instructing the router to use the admin middleware we defined earlier.

- traefik.http.routers.traefik.tls.certresolver=letsencryptresolver

We are also using the letsencryptresolver we defined earlier, to make sure the HTTPS certificate for this route remains valid.

Creating a .env file

Create a new file inside the selfhost directory, and name it .env

If you are going to keep this code in version control (eg: git), make sure to add .env to your .gitignore — or equivalent.

Add the following values:

# .env
EMAIL='your@email.com'
DOMAINNAME='yourdomain.com'
HTTP_BASIC_USER='your_username'
HTTP_BASIC_PWD='your_password'
TRAEFIK_DIR='/mnt/media/traefik'

Replace your@email.com with your real email, and replace yourdomain.com with your real domain.

HTTP Basic Username and Password

The HTTP_BASIC_USERand HTTP_BASIC_PWD are the username and password values you will use to protect the Traefik dashboard. However, you can’t just type in your plain password here. You need to encrypt it first.

To generate an encrypted username/password combination, you can use a site like Online Htpasswd Generator, or run the following command in a terminal:
echo $(htpasswd -nb your_username your_password)

The result will be something like:

your_username:$apr1$qtamuxse$XIWAWKgaBUG03ONJNZ3N80

Copy the portion before the colon (:) to HTTP_BASIC_USER , and the portion after the colon to HTTP_BASIC_PWD .

Eg:

HTTP_BASIC_USER='your_username'
HTTP_BASIC_PWD='$apr1$qtamuxse$XIWAWKgaBUG03ONJNZ3N80'

The acme.json file

If you take another look at our existingdocker-compose.yml, you will see we had defined the following value for our traefik service:

volumes:
- ${TRAEFIK_DIR}/acme.json:/acme.json # stores ACME (HTTPS) certificates

With this line we are mapping the docker container’s /acme.json path to the compute instance’s ${TRAEFIK_DIR}/acme.json path, so it persists between container restarts.

And now in our .env file we are defining:

TRAEFIK_DIR='/mnt/media/traefik'

Which means we will use the /mnt/media/traefik/acme.json file in our compute instance to store our Let’s Encrypt certificates (aka ACME or HTTPS certificates).

So, let’s make sure we create this file beforehand.

SSH into your compute instance and run:

mkdir -p /mnt/media/traefik
touch /mnt/media/traefik/acme.json
chmod 600 /mnt/media/traefik/acme.json

The mkdir command creates a new directory. The touch command creates an empty file.

Since the acme.json file will hold sensitive information, we want to restrict permissions as much as possible. The chmod command lets us configure the file permissions.

If you do not set appropriate file permissions for acme.json, you will run into the following error when running your service:

The ACME resolver \"letsencryptresolver\" is skipped from the resolvers list because: unable to get ACME account: permissions 755 for /acme.json are too open, please use 600

Upload the Docker files to your compute instance

We will use the rsync utility to upload the docker-compose.yml and .env files to our compute instance.

Rsync uses the same credentials as SSH. You can run it with the following format:

rsync -av /path/to/local/dir <ssh_username>@<ip address>:/path/to/remote/dir

For example:
If I created my docker-compose.yml and .env files inside the /home/alexishevia/Projects/selfhost directory in my computer, I would run:

rsync -av /home/alexishevia/Projects/selfhost/ opc@143.47.51.100:/home/opc/selfhost/

And that will upload my files into the /home/opc/selfhost directory in my compute instance.

Running Docker and Traefik

SSH into your compute instance, then run:

cd /home/opc/selfhost
docker-compose up -d

You should see something like this:

[+] Running 5/5
✔ traefik 4 layers [⣿⣿⣿⣿] 0B/0B Pulled 5.7s
✔ c41833b44d91 Pull complete 0.9s
✔ 4336eee5b1b8 Pull complete 1.7s
✔ cb3006acbb0f Pull complete 3.8s
✔ f6ecd274862a Pull complete 3.9s
[+] Running 2/2
✔ Network selfhost_default Created 0.4s
✔ Container selfhost-traefik-1 Started 1.2s

That means our Traefik container is up and running.

We should always take a look at the service logs, just to verify no errors were printed out. Run the following command and confirm there are no error messages printed:

docker-compose logs

You can also double check everything is good by running docker-compose ps, which should print something like this:

NAME                 IMAGE               COMMAND                  SERVICE             CREATED              STATUS              PORTS
selfhost-traefik-1 traefik:v2.10.1 "/entrypoint.sh --en…" traefik About a minute ago Up About a minute 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

Finally, you should be able to visit https://traefik.yourdomain.com , and see the Traefik dashboard, which should look something like this:

Take some time to look around, and get familiar with this dashboard.

You just completed the hardest part of this “Self Hosting Using Docker and a Cloud Provider” series!

In the upcoming posts, we will build upon this base setup to start hosting as many services as we want to.

--

--

Alexis Hevia

Full-Stack Web Developer @ X-Team / Digital Nomad. Se habla español.