Recently I decided to rebuild one of my homelab servers. Previously I was using Nginx as my reverse proxy but I decided to switch to Traefik since I have been using it professionally for some time now. One of the reasons I like Traefik is that it is stupid simple to set up certificates and when I am using it with Docker I don’t have to worry about a bunch of configuration files. If you aren’t familiar with how Traefik works with Docker, here is a brief example of a docker-compose.yaml
version: '3'
services:
reverse-proxy:
# The official v2 Traefik docker image
image: traefik:v2.11
# Enables the web UI and tells Traefik to listen to docker
command:
- --api.insecure=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# Set up LetsEncrypt
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
- [email protected]
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
# Redirect all http requests to https
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.web.http.redirections.entrypoint.permanent=true
- --log=true
- --log.level=INFO
# Needed to request certs via lets encrypt
environment:
- CF_DNS_API_TOKEN=[redacted]
ports:
# The HTTP port
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock:ro
# Used for storing letsencrypt certificates
- ./letsencrypt:/letsencrypt
- ./volumes/traefik/logs:/logs
networks:
- traefik
ots:
image: luzifer/ots
container_name: ots
restart: always
environment:
REDIS_URL: redis://redis:6379/0
SECRET_EXPIRY: "604800"
STORAGE_TYPE: redis
depends_on:
- redis
labels:
- traefik.enable=true
- traefik.http.routers.ots.rule=Host(`ots.example.com`)
- traefik.http.routers.ots.entrypoints=websecure
- traefik.http.routers.ots.tls=true
- traefik.http.routers.ots.tls.certresolver=letsencrypt
- traefik.http.services.ots.loadbalancer.server.port=3000
networks:
- traefik
redis:
image: redis:alpine
restart: always
volumes:
- ./redis-data:/data
networks:
- traefik
networks:
traefik:
external: true
In part one of this series I will be going over some of the basics of Traefik and how dynamic routing works. If you want to skip to the good stuff and get everything configured with Cloudflare, you can skip to part 2.
This example set’s up the primary Traefik container which acts as the ingress controller as well as a handy One Time Secret sharing service I use. Traefik handles routing in Docker via labels. For this to work properly the services that Traefik is trying to route to all need to be on the same Docker network. For this example we created a network called traefik by running the following:
docker network create traefik
Let’s take a look at the labels we applied to the ots
container a little closer:
labels:
- traefik.enable=true
- traefik.http.routers.ots.rule=Host(`ots.example.com`)
- traefik.http.routers.ots.entrypoints=websecure
- traefik.http.routers.ots.tls=true
- traefik.http.routers.ots.tls.certresolver=letsencrypt
- traefik.http.services.ots.loadbalancer.server.port=3000
traefik.enable=true
– This should be pretty self explanatory but it tells Traefik that we want it to know about this service.
traefik.http.routers.ots.rule=Host('ots.example.com') - This is where some of the magic comes in.
Here we are defining a router called ots
. The name is arbitrary in that it doesn’t have to match the name of the service but for our example it does. There are many rules that you can specify but the easiest for this example is host. Basically we are saying that any request coming in for ots.example.com should be picked up by this router. You can find more options for routers in the Traefik docs.
– traefik.http.routers.ots.entrypoints=websecure
– traefik.http.routers.ots.tls=true
– traefik.http.routers.ots.tls.certresolver=letsencrypt
We are using these three labels to tell our router that we want it to use the websecure entrypoint, and that it should use the letsencrypt certresolver to grab it’s certificates. websecure is an arbitrary name that we assigned to our :443 interface. There are multiple ways to configure this, I choose to use the cli format in my traefik config:
“`
command:
- --api.insecure=true
- --providers.docker=true
# Our entrypoint names are arbitrary but these are convention.
# The important part is the port binding that we associate.
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
These last label is optional depending on your setup but it is important to understand as the documentation is a little fuzzy.
– traefik.http.services.ots.loadbalancer.server.port=3000
Here’s how it works. Suppose you have a container that exposes multiple ports. Maybe one of those is a web ui and another is something that you don’t want exposed. By default Traefik will try and guess which port to route requests to. My understanding is that it will try and use the first exposed port. However you can override this functionality by using the label above which will tell Traefik specifically which port you want to route to inside the container.
The service name is derived automatically from the definition in the docker compose file:
ots: # This will become the service name
image: luzifer/ots
container_name: ots