https://github.com/traefik/traefik
The beauty of this solution is that it works programatically.
And thanks to the DNS Challenge, we can get valid https certificates even without port forwarding our home lab.
As you can expect, this also works if you want to host on some VPS.
Why Traefik
SelfHosting Traefik
What we are going to need:
- The Cloudflare API for a zone (one domain) - So that we can validate that we own it and can do the DNS Challenge
We will feed that via the
cf-token
file
- An empty
acme.json
, with proper permissions. That Traefik will fill up automatically
nano ./config/acme.json
chmod 600 ./config/acme.json
- A network for Traefik and any other service that we want to plug it into:
sudo docker network create proxy
- Generate the user and password for Traefik UI:
htpasswd -nb admin password #for the dashboard admin/pwd UI
Last but not least, to thank Jim’s Garage for his video on Traefik v3.3
#sudo docker compose up -d
#sudo rm -r logs
The configuration file looks like so (this is your docker-compose.yml
):
secrets:
cf-token:
file: ./cf-token
services:
traefik:
image: traefik:v3.3 # 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 (For Cloudflare DNS Challenge, used together with CF_DNS_API_TOKEN_FILE)
env_file:
- .env # store other secrets e.g., (For the Traefik Dashboard user/password)
networks:
proxy:
ports:
- 80:80
- 443:443
# - 10000:10000 # optional
# - 33073:33073 # optional
environment:
- TRAEFIK_DASHBOARD_CREDENTIALS=${TRAEFIK_DASHBOARD_CREDENTIALS}
- [email protected] # Cloudflare email - CHANGE THIS!!!
# - CF_DNS_API_TOKEN=YOUR-TOKEN # Cloudflare API Token (Can be used instead of the CF_DNS_API_TOKEN_FILE)
- CF_DNS_API_TOKEN_FILE=/run/secrets/cf-token # see https://doc.traefik.io/traefik/https/acme/#providers (Or just use the above)
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /home/Docker/JimsGarage/Traefikv3/config/traefik.yaml:/traefik.yaml:ro
- /home/Docker/JimsGarage/Traefikv3/config/acme.json:/acme.json
- /home/Docker/JimsGarage/Traefikv3/config/config.yaml:/config.yaml:ro
- /home/Docker/JimsGarage/Traefikv3/logs:/var/log/traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`yourdomain.com`)" #CHANGE THIS!!!
- "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(`yourdomain.com`)" #CHANGE THIS!!!
- "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=yourdomain.com" #CHANGE THIS!!!
- "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.yourdomain.com" #CHANGE THIS!!!
- "traefik.http.routers.traefik-secure.service=api@internal"
networks:
proxy:
external: true # or comment this line to auto create the network
Following this config, you will get Traefik UI at yourdomain.com
and we will be ready to add services as subdomains whateverapp.yourdomain.com
The traefik.yml
api:
dashboard: true
debug: true
entryPoints:
http:
address: ":80"
http:
# middlewares: # uncomment if using CrowdSec - see my video
# - crowdsec-bouncer@file
redirections:
entrypoint:
to: https
scheme: https
https:
address: ":443"
# http:
# middlewares: # uncomment if using CrowdSec - see my video
# - crowdsec-bouncer@file
# tcp:
# address: ":10000"
# apis:
# address: ":33073"
serversTransport:
insecureSkipVerify: true
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: /config.yaml # example provided gives A+ rating https://www.ssllabs.com/ssltest/
certificatesResolvers:
cloudflare:
acme:
# caServer: https://acme-v02.api.letsencrypt.org/directory # production (default)
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory # staging (testing)
email: [email protected] # Cloudflare email (or other provider) #CHANGE THIS!!!
storage: acme.json
dnsChallenge:
provider: cloudflare # change as required
# disablePropagationCheck: true # Some people using Cloudflare note this can solve DNS propagation issues.
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
log:
level: "INFO"
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"
To summarize the steps of selfhosting Traefik
Architecture | Availability | Tag |
---|---|---|
x86-64 | ✅ Available | Multi-Arch |
arm64 | ✅ Available | Multi-Arch |
armhf | ❌ Not Available | Not Supported |
User and Password are defined during setup in the .env
file, as per TRAEFIK_DASHBOARD_CREDENTIALS
variable.
Programatic Https
- Via
config.yml
, we can add services programatically to https.
This will require a restart of the container to see the change of config
Like our Portainer service, which we just have to join to the Proxy network we have created:
docker network connect proxy portainer
And configure like so:
http:
middlewares:
default-security-headers:
headers:
customBrowserXSSValue: 0 # X-XSS-Protection=1; mode=block
contentTypeNosniff: true # X-Content-Type-Options=nosniff
forceSTSHeader: true # Add the Strict-Transport-Security header even when the connection is HTTP
frameDeny: false # X-Frame-Options=deny
referrerPolicy: "strict-origin-when-cross-origin"
stsIncludeSubdomains: true # Add includeSubdomains to the Strict-Transport-Security header
stsPreload: true # Add preload flag appended to the Strict-Transport-Security header
stsSeconds: 3153600 # Set the max-age of the Strict-Transport-Security header (63072000 = 2 years)
contentSecurityPolicy: "default-src 'self'"
customRequestHeaders:
X-Forwarded-Proto: https
https-redirectscheme:
redirectScheme:
scheme: https
permanent: true
routers:
portainer:
entryPoints:
- "https"
rule: "Host(`portainer-demo.jimsgarage.co.uk`)" #CHANGE THIS!!!
middlewares:
- default-security-headers
- https-redirectscheme
tls: {}
service: portainer
services:
portainer:
loadBalancer:
servers:
- url: "https://192.168.200.122:9443" #CHANGE THIS!!!
passHostHeader: true
You will also need the acme.json
file (which will be empty and then populated automatically by Traefik)
touch ./config/acme.json && chmod 600 ./config/acme.json
From the .env
, we will feed the Traefik UI Dashboard credentials
htpasswd -nb admin password_to_be_changed #for the dashboard pwd
# echo $(htpasswd -nB admin) | sed -e s/\\$/\\$\\$/g
And it will look like:
TRAEFIK_DASHBOARD_CREDENTIALS=admin:something_generated
If you are in a rush, you can generate it directly:
PASSWORD="YourReallyStrongPassword123!" && echo "TRAEFIK_DASHBOARD_CREDENTIALS=admin:$(htpasswd -nb admin "$PASSWORD" | sed -e 's/\$/\$\$/g')" > .env
And in this case, you will login with admin/YourReallyStrongPassword123!
to Traefik UI.
- Via labels on the other service:
This method does not require a restart of containers
Just when spinning a new service:
- Comment the ports of the application
- Add the network as external so that it can communicate with Traefik container
- Make sure that the subdomain is pointing to your server internal or public IP that you want to use for the https
- Add proper labels with the desired subdomain
#docker compose -f ytgroqtraefik_docker-compose.yml up -d
#version: '3.8'
#https://github.com/JAlcocerT/Docker/tree/main/AI_Gen/Project_YT_Groq
#version: '3.8'
services:
routed_service:
image: ghcr.io/jalcocert/phidata:yt-groq #A sample streamlit web app
container_name: routed_service
restart: unless-stopped
#command: tail -f /dev/null
# ports:
# - 8501:8501
networks:
- proxy
labels: #with these labels, there is nothing more to configure in the traefik UI nor config.yaml
- "traefik.enable=true"
- "traefik.http.routers.phidata-yt-groq.rule=Host(`routed_service.yourdomain.com`)"
- "traefik.http.routers.phidata-yt-groq.entrypoints=https"
- "traefik.http.routers.phidata-yt-groq.tls=true"
- "traefik.http.routers.phidata-yt-groq.tls.certresolver=cloudflare"
- "traefik.http.services.phidata-yt-groq.loadbalancer.server.port=8501"
- "traefik.docker.network=proxy"
- "traefik.http.routers.phidata-yt-groq.middlewares=default-security-headers@file"
networks:
proxy:
external: true