Traefik and The Beauty of Certificates
Introduction
This will be my first project post. As such I’m posting about something which has become particularly important in my homelab. It’s called Traefik and has helped me do two things:
- Organize the services in my homelab
- Make sure things are secure.
Why Traefik?
The whole point of Traefik is that it can assign a website to your internal (or external with some extra work) services. This wasn’t really an issue for me for a long time. I could get by with just an IP and a port, but once I started having to remember 30 different IPs and I had a notebook 3 pages long with IPs and ports, I knew something had to change. Enter in Jim’s Garage’s Traefik V3 tutorial and I knew immediately I needed this for my homelab.
Traefik seemed like the good choice here not just for the ease of use, but for the customizability. An issue at this point in my tinkering was that I was severely limited in terms of storage, RAM, and CPU horsepower, so a lot of my services were not necessarily on 1 machine. Traefik was fine with this because of its ability to assign services their own subdomain through its dynamic config and with docker labels. This along with the traefik.yaml file makes for a lot of customization.
Labels and Dynamic Config
The main thing needed for Traefik to work is to have a domain name, so I went to Porkbun.com and registered a new one for dirt cheap, spun up a free cloudflare account, and got to work. That part was quite easy. The real beauty of Traefik, as i just mentioned, lies in it’s dynamic config. You simply open up the config file in your editor of choice, add a service and a router.
Ex:
http:
routers:
my-router:
rule: "Host(`coolsite.princessdonut.com`)"
service: my-service
entryPoints:
- websecure
tls:
certResolver: myresolver
services:
my-service:
loadBalancer:
servers:
- url: "http://localhost:8080"
Another thing that Traefik does quite well which I’m not taking advantage of here is the labels. These are things you can put into just about any docker compose file to route it through Traefik. My Traefik docker compose file has these and it allows for a lot of customization. Unfortunately, due to hardware constraints I just spoke on, I can’t do that, but at some point I will definitely migrate to this workflow if I ever have a dedicated docker server.
Once this is all done, you can lock down port 80 on your router and only have things accessible from port 443 (HTTPS). Obviously, you can specify VLANs or specific machines to have permissions for internal port 80, but I won’t go into that here.
Docker Compose
For reference, here’s the docker compose file I used, again, based on the Jim’s Garage tutorial:
secrets:
cf-token:
file: ./cf-token
services:
traefik:
image: traefik:latest # or traefik:v3.3 to pin a version
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true # helps to increase security
secrets:
- cf-token # the secret at the top of this file
env_file:
- .env # store other secrets e.g., dashboard password
networks:
proxy:
ports:
- 80:80 # In case it's needed
- 443:443
# - 10000:10000 # optional
# - 33073:33073 # optional
environment:
- TRAEFIK_DASHBOARD_CREDENTIALS=${TRAEFIK_DASHBOARD_CREDENTIALS}
# - CF_API_EMAIL=your@email.com # Cloudflare email
# - CF_DNS_API_TOKEN=YOUR-TOKEN # Cloudflare API Token
- CF_DNS_API_TOKEN_FILE=/run/secrets/cf-token # see https://doc.traefik.io/traefik/https/acme/#providers
# token file is the proper way to do it
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /path/to/traefik.yaml:/traefik.yaml:ro
- /path/to/traefik/acme.json:/acme.json
- /path/to/traefik/config.yaml:/config.yaml:ro
- /path/to/traefik/logs:/var/log/traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.princessdonut.com`)"
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}"
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.princessdonut.com`)"
- "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik-secure.tls.domains[0].main=princessdonut.com"
- "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.princessdonut.com"
- "traefik.http.routers.traefik-secure.service=api@internal"
networks:
proxy:
external: true # or comment this line to auto create the network
Something you may notice is that I have my cloudflare credentials stored in a secrets file. This ensures proper permissions and in the case I somehow get hacked, that file is all the more difficult to look at.
The Effect and Next Steps
I can’t overstate how much easier this is, especially for people who aren’t as tech literate living in the same home. Instead of giving someone an IP and a port to remember, like 10.0.30.327:8756, just point them to something like coolsite.princessdonut.io and they’ll remember it easily and with no fuss.
On the off-chance someone get’s into my network, everything has SSL, everything is HTTPS, so that eliminates a large portion of security vulnerabilities.
Like I mentioned before, I will be largely migrating from the dynamic config to the traefik labels by moving as much as I can over to a dedicated docker server, but for now this hybrid apporach works for me!
