Traefik v2

guide by examples

logo

edit - discovered caddy, seems simpler, here is its guide.

requirements

  • have docker running somewhere
  • have a domain example.com
  • use cloudflare to manage DNS of the domain
  • have 80/443 ports open

chapters

  1. traefik routing to docker containers
  2. traefik routing to a local IP addresses
  3. middlewares
  4. let's encrypt certificate HTTP challenge
  5. let's encrypt certificate DNS challenge
  6. redirect HTTP traffic to HTTPS

#1 traefik routing to various docker containers

traefik-dashboard-pic

  • create a new docker network docker network create traefik_net.
    Traefik and the containers need to be on the same network. Compose creates one automatically, but that fact is hidden and there is potential for a fuck up later on. Better to just create own network and set it as default in every compose file.

    extra info: use docker network inspect traefik_net to see containers connected to that network

  • create traefik.yml
    This file contains so called static traefik configuration.
    In this basic example there are just few self-explanatory settings.
    Since exposedbydefault is set to false, a label "traefik.enable=true" will be needed for containers that should be routed by traefik.
    This file will be passed to a docker container using bind mount, this will be done when we get to docker-compose.yml for traefik.

    traefik.yml

    ## STATIC CONFIGURATION
    log:
      level: INFO
    
    api:
      insecure: true
      dashboard: true
    
    entryPoints:
      web:
        address: ":80"
    
    providers:
      docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
    

    later on when traefik container is running, use command docker logs traefik and check if there is a notice stating: "Configuration loaded from file: /traefik.yml". You don't want to be the moron who makes changes to traefik.yml and it does nothing because the file is not actually being used.

  • create .env file that will contain environmental variables.
    Domain names, api keys, ip addresses, passwords,... whatever is specific for one case and different for another, all of that ideally goes here. These variables will be available for docker-compose when running the docker-compose up command.
    This allows compose files to be moved from system to system more freely and changes are done to the .env file, so there's a smaller possibility for a fuckup of forgetting to change domain name in some host rule in a big ass compose file or some such.

    .env

    MY_DOMAIN=example.com
    DEFAULT_NETWORK=traefik_net
    

    extra info:
    Command docker-compose config shows how the compose will look with the variables filled in.

    These variables are only filled in during the compose initial building of container. If an env variable should be available also inside the running container, it needs to be declared in the environment section of the compose file.

  • create traefik-docker-compose.yml file.
    It's a simple typical compose file.
    Port 80 is mapped since we want traefik to be in charge of what comes on it - using it as an entrypoint. Port 8080 is for dashboard where traefik shows info. Mount of docker.sock is needed, so it can actually do its job interacting with docker. Mount of traefik.yml is what gives the static traefik configuration. The default network is set to the one created in the first step, as it will be set in all other compose files.

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        ports:
          - "80:80"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • run traefik-docker-compose.yml
    docker-compose -f traefik-docker-compose.yml up -d will start the traefik container.

    traefik is running, you can check it at the ip:8080 where you get the dashboard.
    Can also check out logs with docker logs traefik.

    extra info:
    Typically you see guides having just a single compose file called docker-compose.yml with several services/containers in it. Then it's just docker-compose up -d to start it all. You don't even need to bother defining networks when it is all one compose. But this time I prefer small and separate steps when learning new shit. So that's why going with custom named docker-compose files as it allows easier separation.

    extra info2:
    What you can also see in tutorials is no mention of traefik.yml and stuff is just passed from docker-compose using traefik's commands or labels.
    like this: command: --api.insecure=true --providers.docker
    But that way compose files look bit more messy and you still can't do everything from there, you still sometimes need that damn traefik.yml.
    So... for now, going with nicely structured readable traefik.yml

  • add labels to containers that traefik should route.
    Here are examples of whoami, nginx, apache, portainer.

    - "traefik.enable=true"

    enables traefik

    - "traefik.http.routers.whoami.entrypoints=web"

    defines router named whoami that listens on entrypoint web(port 80)

    - "traefik.http.routers.whoami.rule=Host(whoami.$MY_DOMAIN)"

    defines a rule for this whoami router, specifically that when url equals whoami.example.com (the domain name comes from the .env file), that it means for router to do its job and route it to a service.

    Nothing else is needed, traefik knows the rest from the fact that these labels are coming from context of a docker container.

    whoami-docker-compose.yml

    version: "3.7"
    
    services:
      whoami:
        image: "containous/whoami"
        container_name: "whoami"
        hostname: "whoami"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoami.entrypoints=web"
          - "traefik.http.routers.whoami.rule=Host(`whoami.$MY_DOMAIN`)"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    nginx-docker-compose.yml

    version: "3.7"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        hostname: nginx
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=web"
          - "traefik.http.routers.nginx.rule=Host(`nginx.$MY_DOMAIN`)"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    apache-docker-compose.yml

    version: "3.7"
    
    services:
      apache:
        image: httpd:latest
        container_name: apache
        hostname: apache
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.apache.entrypoints=web"
          - "traefik.http.routers.apache.rule=Host(`apache.$MY_DOMAIN`)"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    portainer-docker-compose.yml

    version: "3.7"
    
    services:
      portainer:
        image: portainer/portainer
        container_name: portainer
        hostname: portainer
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock:ro
          - portainer_data:/data
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.portainer.entrypoints=web"
          - "traefik.http.routers.portainer.rule=Host(`portainer.$MY_DOMAIN`)"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
    volumes:
      portainer_data:
    
    
  • run the damn containers
    ignore some orphans talk, it's cuz these compose files are in the same directory and compose uses parent directory name for name of compose project

    docker-compose -f whoami-docker-compose.yml up -d
    docker-compose -f nginx-docker-compose.yml up -d
    docker-compose -f apache-docker-compose.yml up -d
    docker-compose -f portainer-docker-compose.yml up -d

    extra info:
    to stop all containers running: docker stop $(docker ps -q)

#2 traefik routing to a local IP addresses

When url should aim at something other than a docker container.

simple-network-diagram-pic

  • define a file provider, add required routing and service

    What is needed is a router that catches some url and route it to some IP.
    Previous examples shown how to catch whatever url, on port 80, but no one told it what to do when something fits the rule. Traefik just knew since it was all done using labels in the context of a container and thanks to docker being set as a provider in traefik.yml.
    For this "sending traffic at some IP" a traefik service is needed, and to define traefik service a new provider is required, a file provider - just a fucking stupid file that tells traefik what to do.
    Somewhat common is to set traefik.yml itself as a file provider so thats what will be done.
    Under providers theres a new file section and traefik.yml itself is set.
    Then the dynamic configuration stuff is added.
    A router named route-to-local-ip with a simple subdomain hostname rule. What fits that rule, in this case exact url test.example.com, is send to the loadbalancer service which just routes it to a specific IP and specific port.

    traefik.yml

    ## STATIC CONFIGURATION
    log:
      level: INFO
    
    api:
      insecure: true
      dashboard: true
    
    entryPoints:
      web:
        address: ":80"
    
    providers:
      docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
      file:
        filename: "traefik.yml"
    
    ## DYNAMIC CONFIGURATION
    http:
      routers:
        route-to-local-ip:
          rule: "Host(`test.example.com`)"
          service: route-to-local-ip-service
          priority: 1000
          entryPoints:
            - web
    
      services:
        route-to-local-ip-service:
          loadBalancer:
            servers:
              - url: "http://10.0.19.5:80"
    

    Priority of the router is set to 1000, a very high value, beating any possible other routers.

    extra info:
    Unfortunately the .env variables are not working here, otherwise domain name in host rule and that IP would come from variables. So heads up that you will definitely forget to change these.

  • run traefik-docker-compose and test if it works

    docker-compose -f traefik-docker-compose.yml up -d

#3 middlewares

Example of an authentication middleware for any container.

logic-pic

  • create a new file - users_credentials containing username:passwords pairs, htpasswd style
    Bellow example has password krakatoa set to all 3 accounts

    users_credentials

    me:$apr1$L0RIz/oA$Fr7c.2.6R1JXIhCiUI1JF0
    admin:$apr1$ELgBQZx3$BFx7a9RIxh1Z0kiJG0juE/
    bastard:$apr1$gvhkVK.x$5rxoW.wkw1inm9ZIfB0zs1
    
  • mount users_credentials in traefik-docker-compose.yml

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        ports:
          - "80:80"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
          - "./users_credentials:/users_credentials:ro"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • add two labels to any container that should have authentication

    • The first label attaches new middleware called auth-middleware to an already existing whoami router.

    • The second label gives this middleware type basicauth, and tells it where is the file it should use to authenticate users.

      No need to mount the users_credentials here, it's traefik that needs that file and these labels are a way to pass info to traefik, what it should do in context of containers.

    whoami-docker-compose.yml

    version: "3.7"
    
    services:
      whoami:
        image: "containous/whoami"
        container_name: "whoami"
        hostname: "whoami"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoami.entrypoints=web"
          - "traefik.http.routers.whoami.rule=Host(`whoami.$MY_DOMAIN`)"
          - "traefik.http.routers.whoami.middlewares=auth-middleware"
          - "traefik.http.middlewares.auth-middleware.basicauth.usersfile=/users_credentials"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    nginx-docker-compose.yml

    version: "3.7"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        hostname: nginx
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=web"
          - "traefik.http.routers.nginx.rule=Host(`nginx.$MY_DOMAIN`)"
          - "traefik.http.routers.nginx.middlewares=auth-middleware"
          - "traefik.http.middlewares.auth-middleware.basicauth.usersfile=/users_credentials"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • run the damn containers and now there is login and password needed

    docker-compose -f traefik-docker-compose.yml up -d
    docker-compose -f whoami-docker-compose.yml up -d
    docker-compose -f nginx-docker-compose.yml up -d

#4 let's encrypt certificate, HTTP challenge

letsencrypt-http-challenge-pic

My understanding of the process, simplified.

LE - Let's Encrypt. A service that gives out free certificates
Certificate - a cryptographic key stored in a file on the server, allows encrypted communication and confirms the identity
ACME - a protocol(precisely agreed way of communication) to negotiate certificates from LE. It is part of traefik.
DNS - servers on the internet, translate domain names in to ip address

Traefik uses ACME to ask LE for a certificate for a specific domain, like example.com. LE answers with some random generated text that traefik puts at a specific place on the server. LE then asks DNS internet servers for example.com and that points to some IP address. LE looks at that IP address through ports 80/443 for the file containing that random text.

If it's there then this proves that whoever asked for the certificate controls both the server and the domain, since it showed control over DNS records. Certificate is given and is valid for 3 months, traefik will automatically try to renew when less than 30 days is remaining.

Now how to actually get it done.

  • create an empty acme.json file with 600 permissions

    This file will store the certificates and all the info about them.

    touch acme.json && chmod 600 acme.json

  • add 443 entrypoint and certificate resolver to traefik.yml

    In entrypoint section new entrypoint is added called websecure, port 443

    certificatesResolvers is a configuration section that tells traefik how to use acme resolver to get certificates.

    certificatesResolvers:
      lets-encr:
        acme:
          #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
          storage: acme.json
          email: whatever@gmail.com
          httpChallenge:
            entryPoint: web
    
    • the name of the resolver is lets-encr and uses acme
    • commented out staging caServer makes LE issue a staging certificate, it is an invalid certificate and wont give green lock but has no limitations, so it's good for testing. If it's working it will say issued by let's encrypt.
    • Storage tells where to store given certificates - acme.json
    • The email is where LE sends notification about certificates expiring
    • httpChallenge is given an entrypoint, so acme does http challenge over port 80

    That is all that is needed for acme

    traefik.yml

    ## STATIC CONFIGURATION
    log:
      level: INFO
    
    api:
      insecure: true
      dashboard: true
    
    entryPoints:
      web:
        address: ":80"
      websecure:
        address: ":443"
    
    providers:
      docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
    
    certificatesResolvers:
      lets-encr:
        acme:
          #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
          storage: acme.json
          email: whatever@gmail.com
          httpChallenge:
            entryPoint: web
    
  • expose/map port 443 and mount acme.json in traefik-docker-compose.yml

    Notice that acme.json is not :ro - read only

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        env_file:
          - .env
        ports:
          - "80:80"
          - "443:443"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
          - "./acme.json:/acme.json"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • add required labels to containers
    compared to just plain http from first chapter, it is just changing router's entryPoint from web to websecure and assigning certificate resolver named lets-encr to the existing router

    whoami-docker-compose.yml

    version: "3.7"
    
    services:
      whoami:
        image: "containous/whoami"
        container_name: "whoami"
        hostname: "whoami"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoami.entrypoints=websecure"
          - "traefik.http.routers.whoami.rule=Host(`whoami.$MY_DOMAIN`)"
          - "traefik.http.routers.whoami.tls.certresolver=lets-encr"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    nginx-docker-compose.yml

    version: "3.7"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        hostname: nginx
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=websecure"
          - "traefik.http.routers.nginx.rule=Host(`nginx.$MY_DOMAIN`)"
          - "traefik.http.routers.nginx.tls.certresolver=lets-encr"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • run the damn containers
    give it a minute
    containers will now work only over https and have the greenlock

    extra info:
    check content of acme.json
    delete acme.json if you want fresh start

#5 let's encrypt certificate DNS challenge on cloudflare

letsencrypt-dns-challenge-pic

My understanding of the process, simplified.

LE - Let's Encrypt. A service that gives out free certificates
Certificate - a cryptographic key stored in a file on the server, allows encrypted communication and confirms the identity
ACME - a protocol(precisely agreed way of communication) to negotiate certificates from LE. It is part of traefik.
DNS - servers on the internet, translate domain names in to ip address

Traefik uses ACME to ask LE for a certificate for a specific domain, like example.com. LE answers with some random generated text that traefik puts as a new DNS TXT record. LE then checks example.com DNS records to see if the text is there.

If it's there then this proves that whoever asked for the certificate controls the domain. Certificate is given and is valid for 3 months. Traefik will automatically try to renew when less than 30 days is remaining.

Benefit over httpChallenge is ability to have wild card certificates. These are certificates that validate all subdomains *.example.com
Also no ports are needed to be open.

But traefik needs to be able to make these automated changes to DNS records, so there needs to be support for this from whoever manages sites DNS. Thats why going with cloudflare.

Now how to actually get it done.

  • add type A DNS records for all planned subdomains

    [whoami, nginx, *] are used example subdomains, each one should have A-record pointing at traefik IP

  • create an empty acme.json file with 600 permissions

    touch acme.json && chmod 600 acme.json

  • add 443 entrypoint and certificate resolver to traefik.yml

    In entrypoint section new entrypoint is added called websecure, port 443

    certificatesResolvers is a configuration section that tells traefik how to use acme resolver to get certificates.

    certificatesResolvers:
    lets-encr:
      acme:
        #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
        email: whatever@gmail.com
        storage: acme.json
        dnsChallenge:
          provider: cloudflare
          resolvers:
            - "1.1.1.1:53"
            - "8.8.8.8:53"
    
    • the name of the resolver is lets-encr and uses acme
    • commented out staging caServer makes LE issue a staging certificate, it is an invalid certificate and wont give green lock but has no limitations, if it's working it will say issued by let's encrypt.
    • Storage tells where to store given certificates - acme.json
    • The email is where LE sends notification about certificates expiring
    • dnsChallenge is specified with a provider, in this case cloudflare. Each provider needs differently named environment variable in the .env file, but thats later, here it just needs the name of the provider
    • resolvers are IP of well known DNS servers to use during challenge

    traefik.yml

    ## STATIC CONFIGURATION
    log:
      level: INFO
    
    api:
      insecure: true
      dashboard: true
    
    entryPoints:
      web:
        address: ":80"
      websecure:
        address: ":443"
    
    providers:
      docker:
        endpoint: "unix:///var/run/docker.sock"
        exposedByDefault: false
    
    certificatesResolvers:
      lets-encr:
        acme:
          #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
          email: whatever@gmail.com
          storage: acme.json
          dnsChallenge:
            provider: cloudflare
            resolvers:
              - "1.1.1.1:53"
              - "8.8.8.8:53"
    
  • to the .env file add required variables
    We know what variables to add based on the list of supported providers.
    For cloudflare variables are

    • CF_API_EMAIL - cloudflare login
    • CF_API_KEY - global api key

    .env

    MY_DOMAIN=example.com
    DEFAULT_NETWORK=traefik_net
    CF_API_EMAIL=whateverbastard@gmail.com
    CF_API_KEY=8d08c87dadb0f8f0e63efe84fb115b62e1abc
    
  • expose/map port 443 and mount acme.json in traefik-docker-compose.yml

    Notice that acme.json is not :ro - read only

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        env_file:
          - .env
        ports:
          - "80:80"
          - "443:443"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
          - "./acme.json:/acme.json"
    
      networks:
        default:
          external:
            name: $DEFAULT_NETWORK
    
  • add required labels to containers
    compared to just plain http from the first chapter

    • router's entryPoint is switched from web to websecure
    • certificate resolver named lets-encr assigned to the router
    • a label defining main domain that will get the certificate, in this it is whoami.example.com, domain name pulled from .env file

    whoami-docker-compose.yml

    version: "3.7"
    
    services:
      whoami:
        image: "containous/whoami"
        container_name: "whoami"
        hostname: "whoami"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoami.entrypoints=websecure"
          - "traefik.http.routers.whoami.rule=Host(`whoami.$MY_DOMAIN`)"
          - "traefik.http.routers.whoami.tls.certresolver=lets-encr"
          - "traefik.http.routers.whoami.tls.domains[0].main=whoami.$MY_DOMAIN"
    
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    nginx-docker-compose.yml

    version: "3.7"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        hostname: nginx
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=websecure"
          - "traefik.http.routers.nginx.rule=Host(`nginx.$MY_DOMAIN`)"
          - "traefik.http.routers.nginx.tls.certresolver=lets-encr"
          - "traefik.http.routers.nginx.tls.domains[0].main=nginx.$MY_DOMAIN"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • run the damn containers
    docker-compose -f traefik-docker-compose.yml up -d
    docker-compose -f whoami-docker-compose.yml up -d
    docker-compose -f nginx-docker-compose.yml up -d

  • Fuck that, the whole point of DNS challenge is to get wildcards!
    fair enough
    so for wildcard these labels go in to traefik compose.

    • same lets-encr certificateresolver is used as before, the one defined in traefik.yml
    • the wildcard for subdomains(*.example.com) is set as the main domain to get certificate for
    • the naked domain(just plain example.com) is set as sans(Subject Alternative Name)

    again, you do need *.example.com and example.com set in your DNS control panel as A-record pointing to IP of traefik

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        env_file:
          - .env
        ports:
          - "80:80"
          - "443:443"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
          - "./acme.json:/acme.json"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.traefik.tls.certresolver=lets-encr"
          - "traefik.http.routers.traefik.tls.domains[0].main=*.$MY_DOMAIN"
          - "traefik.http.routers.traefik.tls.domains[0].sans=$MY_DOMAIN"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    Now if a container wants to be accessible as a subdomain, it just needs a regular router that has rule for the url, be on 443 port entrypoint, and use the same lets-encr certificate resolver

    whoami-docker-compose.yml

    version: "3.7"
    
    services:
      whoami:
        image: "containous/whoami"
        container_name: "whoami"
        hostname: "whoami"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoami.entrypoints=websecure"
          - "traefik.http.routers.whoami.rule=Host(`whoami.$MY_DOMAIN`)"
          - "traefik.http.routers.whoami.tls.certresolver=lets-encr"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    nginx-docker-compose.yml

    version: "3.7"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        hostname: nginx
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=websecure"
          - "traefik.http.routers.nginx.rule=Host(`nginx.$MY_DOMAIN`)"
          - "traefik.http.routers.nginx.tls.certresolver=lets-encr"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

    Here is apache but this time run on the naked domain example.com

    apache-docker-compose.yml

    version: "3.7"
    
    services:
      apache:
        image: httpd:latest
        container_name: apache
        hostname: apache
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.apache.entrypoints=websecure"
          - "traefik.http.routers.apache.rule=Host(`$MY_DOMAIN`)"
          - "traefik.http.routers.apache.tls.certresolver=lets-encr"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    

#6 redirect http traffic to https

padlocks-pic

http stops working with https setup, better to redirect http(80) to https(443).
Traefik has special type of middleware for this purpose - redirectscheme.

There are several places where this redirect can be declared, in traefik.yml, in the dynamic section when traefik.yml itself is set as a file provider.
Or using labels in any running container, this example does it in traefik compose.

  • add new router and a redirect scheme using labels in traefik compose

    - "traefik.enable=true"

    enables traefik on this traefik container, not that there is need of the typical routing to a service here, but other labels would not work without this

    - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"

    creates new middleware called redirect-to-https, type "redirectscheme" and assigns it scheme https.

    - "traefik.http.routers.redirect-https.rule=hostregexp(`{host:.+}`)"

    creates new router called redirect-https, with a regex rule that catches any and every incoming request

    - "traefik.http.routers.redirect-https.entrypoints=web"

    declares on which entrypoint this router listens - web(port 80)

    - "traefik.http.routers.redirect-https.middlewares=redirect-to-https"

    assigns the freshly created redirectscheme middleware to this freshly created router.

    So to sum it up, when a request comes at port 80, router that listens at that entrypoint looks at it. If it fits the rule, and it does because everything fits that rule, it goes to the next step. Ultimately it should get to a service, but if there is middleware declared, that middleware goes first, and since middleware is there, and it is some redirect scheme, it never reaches any service, it gets redirected using https scheme, which I guess is stating - go for port 443.

    Here is the full traefik compose, with dns challenge labels from previous chapter included:

    traefik-docker-compose.yml

    version: "3.7"
    
    services:
      traefik:
        image: "traefik:v2.1"
        container_name: "traefik"
        hostname: "traefik"
        env_file:
          - .env
        ports:
          - "80:80"
          - "443:443"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./traefik.yml:/traefik.yml:ro"
          - "./acme.json:/acme.json"
        labels:
          - "traefik.enable=true"
          ## DNS CHALLENGE
          - "traefik.http.routers.traefik.tls.certresolver=lets-encr"
          - "traefik.http.routers.traefik.tls.domains[0].main=*.$MY_DOMAIN"
          - "traefik.http.routers.traefik.tls.domains[0].sans=$MY_DOMAIN"
          ## HTTP REDIRECT
          - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
          - "traefik.http.routers.redirect-https.rule=hostregexp(`{host:.+}`)"
          - "traefik.http.routers.redirect-https.entrypoints=web"
          - "traefik.http.routers.redirect-https.middlewares=redirect-to-https"
    
    networks:
      default:
        external:
          name: $DEFAULT_NETWORK
    
  • run the damn containers and now http://whoami.example.com is immediately changed to https://whoami.example.com

stuff to checkout