Traefik
Purpose: A traefik reverse proxy is a server that sits between your network firewall and servers hosting various web services on your private network(s). Traefik automatically handles the creation of Let's Encrypt SSL certificates if you have a domain registrar that is supported by Traefik such as CloudFlare; by leveraging API keys, Traefik can automatically make the DNS records for Let's Encrypt's DNS "challenges" whenever you add a service behind the Traefik reverse proxy.
Assumptions
This Traefik deployment document assumes you have deployed Portainer to either a Rocky Linux or Ubuntu Server environment. Other docker-compose friendly operating systems have not been tested, so your mileage may vary regarding successful deployment ouside of these two operating systems.
Portainer makes deploying and updating Traefik so much easier than via a CLI. It's also much more intuitive.
Deployment on Portainer¶
- Login to Portainer (e.g. https://
:9443) - Navigate to "Environment (usually "local") > Stacks > "+ Add Stack""
- Enter the following
docker-compose.yml
and.env
environment variables into the webpage - When you have finished making adjustments to the environment variables (and docker-compose data if needed), click the "Deploy the Stack" button
Get DNS Registrar API Keys BEFORE DEPLOYMENT
When you are deploying this container, you have to be mindful to set valid data for the environment variables related to the DNS registrar. In this example, it is CloudFlare.
CF_API_EMAIL=nicole.rappe@bunny-lab.io
CF_API_KEY=REDACTED-CLOUDFLARE-DOMAIN-API-KEY
If these are not set, Traefik will still work, but SSL certificates will not be issued from Let's Encrypt, and SSL traffic will be terminated using a self-signed Traefik-based certificate, which is only good for local non-production testing.
If you plan on using HTTP-based challenges, you will need to make the following changes in the docker-compose.yml data:
- Un-comment
"--certificatesresolvers.myresolver.acme.tlschallenge=true"
- Comment-out
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
- Comment-out
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
- Lastly, you need to ensure that port 80 on your firewall is opened to the IP of the Traefik Reverse Proxy to allow Let's Encrypt to do TLS-based challenges.
Stack Deployment Information¶
version: "3.3"
services:
traefik:
image: "traefik:latest"
restart: always
container_name: "traefik-bunny-lab-io"
ulimits:
nofile:
soft: 65536
hard: 65536
labels:
- "traefik.http.routers.traefik-proxy.middlewares=my-buffering"
- "traefik.http.middlewares.my-buffering.buffering.maxRequestBodyBytes=104857600"
- "traefik.http.middlewares.my-buffering.buffering.maxResponseBodyBytes=104857600"
- "traefik.http.middlewares.my-buffering.buffering.memRequestBodyBytes=2097152"
- "traefik.http.middlewares.my-buffering.buffering.memResponseBodyBytes=2097152"
- "traefik.http.middlewares.my-buffering.buffering.retryExpression=IsNetworkError() && Attempts() <= 2"
command:
# Globals
- "--log.level=ERROR"
- "--api.insecure=true"
- "--global.sendAnonymousUsage=false"
# Docker
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# File Provider
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
# Entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure" # Redirect HTTP to HTTPS
- "--entrypoints.web.http.redirections.entrypoint.scheme=https" # Redirect HTTP to HTTPS
- "--entrypoints.web.http.redirections.entrypoint.permanent=true" # Redirect HTTP to HTTPS
# LetsEncrypt
### - "--certificatesresolvers.myresolver.acme.tlschallenge=true" # Enable if doing Port 80 Let's Encrypt Challenges
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true" # Disable if doing Port 80 Let's Encrypt Challenges
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" # Disable if doing Port 80 Let's Encrypt Challenges
- "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
# Keycloak plugin configuration
- "--experimental.plugins.keycloakopenid.moduleName=github.com/Gwojda/keycloakopenid" # Optional if you have Keycloak Deployed
- "--experimental.plugins.keycloakopenid.version=v0.1.34" # Optional if you have Keycloak Deployed
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/srv/containers/traefik/letsencrypt:/letsencrypt"
- "/srv/containers/traefik/config:/etc/traefik"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/srv/containers/traefik/cloudflare:/cloudflare"
networks:
docker_network:
ipv4_address: 192.168.5.29
environment:
- CF_API_EMAIL=${CF_API_EMAIL}
- CF_API_KEY=${CF_API_KEY}
extra_hosts:
- "mail.bunny-lab.io:192.168.3.13" # Just an Example
networks:
default:
external:
name: docker_network
docker_network:
external: true
[email protected]
CF_API_KEY=REDACTED-CLOUDFLARE-DOMAIN-API-KEY
[email protected]
Info
There is a distinction between the "Global API Key" and a "Token API Key". The main difference being that the "Global API Key" can change anything in Cloudflare, while the "Token API Key" can only change what it was granted delegated permissions to.
Adding Servers / Services to Traefik¶
Traefik operates in two ways, the first is labels, while the second are dynamic configuration files. We will go over each below.
Docker-Compose Labels¶
The first is that it reads "labels" from the docker-compose file of any deployed containers on the same host as Traefik. These labels typically look something like the following:
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`example.bunny-lab.io`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
- "traefik.http.services.gitea.loadbalancer.server.port=8080"
By adding these labels to any container on the same server as Traefik, traefik will automatically "adopt" this service and route traffic to it as well as assign an SSL certificate to it from Let's Encrypt. The only downside is as mentioned above, if you are dealing with something that is not just a container, or maybe a container on a different physical server, you need to rely on dynamic configuration files, such as the one seen below.
Dynamic Configuration Files¶
Dynamic configuration files exist under the Traefik container located at /etc/traefik/dynamic
. Any *.yml
files located in this folder will be hot-loaded anytime they are modified. This makes it convenient to leverage something such as the Git Repo Updater container to leverage Gitea to push configuration files from Git into the production environment, saving yourself headache and enabling version control over every service behind the reverse proxy.
An example of a dynamic configuration file would look something like this:
http:
routers:
example:
entryPoints:
- websecure
tls:
certResolver: letsencrypt
http2:
service: example
rule: Host(`example.bunny-lab.io`)
services:
example:
loadBalancer:
servers:
- url: http://192.168.5.70:8080
passHostHeader: true
You can see the similarities between the labeling method and how you designate the proxy name example.bunny-lab.io
the internal ip address 192.168.5.70
the protocol to request the data from the service internally http
, and the port the server is listening on internally 8080
. If you want to know more about the parameters such as passHostHeader: true
then you will need to do some of your own research into it.
Service Naming Considerations
When you deploy a service into a Traefik-based reverse proxy, the name of the router
and service
have to be unique. The router can have the same name as the service, such as example
, but I recommend naming the services to match the FQDN of the service itself.
For example, remote.bunny-lab.io
would be written as remote-bunny-lab-io
. This keeps things organized and easy to read if you are troubleshooting things in Traefik's logs or webUI. The complete configuration file would look like the example below:
http:
routers:
remote-bunny-lab-io:
entryPoints:
- websecure
tls:
certResolver: letsencrypt
http2:
service: remote-bunny-lab-io
rule: Host(`remote.bunny-lab.io`)
services:
remote-bunny-lab-io:
loadBalancer:
servers:
- url: http://192.168.5.70:8080
passHostHeader: true