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:

  1. 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

  1. An empty acme.json, with proper permissions. That Traefik will fill up automatically
nano ./config/acme.json
chmod 600 ./config/acme.json
  1. A network for Traefik and any other service that we want to plug it into:
sudo docker network create proxy
  1. 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

  1. 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.

  1. 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

Conclusions