I could have titled this post as “The evolution of a website”. This blog was started back in 2015 and at that time was hosted on a virtual Ubuntu 14.04 instance. I also had my personal site on yet another instance. Of course it worked well but was a waste of resources for a lightly used blog mainly by me! There were upgrades to newer versions of OS along the way but the big move was to Docker. I finally moved both sites to a single virtual instance. What a superb piece of software Docker really is. Of course the whole containerisation phenomenon continues to gather pace. My first implementation of websites on Docker was not using Traefik but an Nginx proxy as the ingress point which was trivial to implement. Traefik 1.7 was also fairly easy as there are a lot of examples out on the web. The guys that built Traefik have had a major redesign though on moving to version 2.0 and the documentation can only be described as perhaps lacking in ‘example based documentation.’ It certainly took some time to understand so I will hopefully save others some time with this post.

The first thing to understand is a key difference in the Traefik config implementation. There is ‘static’ configuration and ‘dynamic’ configuration. To add to the confusion there is also multiple methods you can use. Bear in mind they are mutually exclusive, e.g. you may only use one.

For the static configuration, you may use either: TOML or YAML as a file type provider. You may also use CLI which is still technically within a YAML but as part of the Traefik configuration file.

For the dynamic configuration, you have even more options, but if we are talking about a simple blog on a single server using a file type then it is either: TOML or YAML. This way you can save repeating the same code endlessly in CLI config commands.

I went for CLI and TOML configuration respectively. Now for the bit that most people are looking for! The following is the code for the Traefik 2 container:

Note, I’m using docker-compose rather than the incredibly long winded docker run commands.

~/traefik/docker-compose.yml

version: "3.3"

services:

  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    restart: "always"
    command:
      - "--global.sendAnonymousUsage"
        #- "--log.level=DEBUG"
      - "--log.filePath=/var/log/traefik.log"
      - "--accesslog=true"
      - "--accesslog.filePath=/var/log/access.log"
      - "--accesslog.bufferingsize=100"
        #- "--api.insecure=true"
        #- "--api=true"
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file.directory=/configuration"
      - "--providers.file.filename=dynamic_conf.toml"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
        #- "--certificatesresolvers.mytlschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.mytlschallenge.acme.email=postmaster@yourdomain.com"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    network_mode: "host"
    volumes:
      - logs:/var/log
      - "./letsencrypt:/letsencrypt"
      - "./configuration:/configuration"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
volumes:
  logs: {}

Note the commented commands. The api should be secured as per the dashboard if it is used, if it isn’t though disable it. The letsencrypt staging provider should be used while you are testing. You can then comment out once you are up and running.

This will get you a functioning traefik container with a directory named ‘configuration’ to store your dynamic configuration file. There is also a ‘letsencrypt’ directory to store your certificates file. I’ve also added a mounted logs directory to write logs to.

The next file is the dynamic TOML configuration file. This file is dynamic because it may be changed while the reverse proxy is running and the software will monitor the changes made and react accordingly.

~/traefik/configuration/dynamic_conf.toml

[http]

  [http.routers.my-api]
    entryPoints = ["websecure"]
    rule = "Host(`traefik.yourdomain.com`)"
    service = "api@internal"
    middlewares = ["secured"]
    [http.routers.my-api.tls]
      options = "default"
      certResolver = "mytlschallenge"

  [http.middlewares]
 
    [http.middlewares.secured.chain]
      middlewares = ["safe-ipwhitelist", "auth"]

    [http.middlewares.secureHeader.headers]
      frameDeny = true
      sslRedirect = true
      stsSeconds = 31536000
      stsPreload = true
      stsIncludeSubdomains = true
  
    [http.middlewares.auth.basicAuth]
      users = [
      "secure_user:$2y$05$AM4KZ7OspJ./M7zV6zmLkevRVekRvnaZUcaSMFOVXfT5.ugcQyFVq"
      ]

    [http.middlewares.safe-ipwhitelist.ipWhiteList]
      sourceRange = ["192.168.1.0/24"]

[tls]

  [tls.options]
    [tls.options.default]
      minVersion = "VersionTLS12"
      cipherSuites = [
        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
        "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
        "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
        "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      ]
      sniStrict = true

There is a fair amount going on this file. The routers section defines the dashboard and the associated configuration. It also introduces chaining of middleware to secure access to the dashboard. The middlewares section defines both the chain and the individual middlewares. I will warn you against using the secureHeader middleware until you are sure that you have implemented your site correctly. Note also, the whitelist currently contains a single RFC 1918 prefix. This will not allow you access to the dashboard on an internet address as it is unroutable address space. You should replace this with an address range you would actually like to access the dashboard from if you wish to restrict access this way. You should also make your own user and password. Use htpasswd for this, e.g:

htpasswd -nb -B secure_user dontusethispassword

The final section is the TLS section. I have made this deliberately secure keeping TLS 1.2 as the minimum version with the included cipher suites. This will help ensure you can achieve an A+ score on:

https://www.ssllabs.com

The Traefik container uses host docker networking. I have implemented it this way to ensure that I see the actual addresses of users who access this site. This does mean however you should be in full control of the firewall on your instance. The default way would be via bridge but then you would see the bridge address as accessing your site.

Now that you have a functioning Traefik 2 container. You should be able to access the dashboard. (Don’t forget your DNS updates) Now we can look at the really great thing that Traefik does. Automatic routing of web services to the correct service. Next I’ll provide a working configuration of a docker WordPress site.

~/yourdomain.com/docker-compose.yml

version: '3.3'

services:
  db:
    image: mariadb
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - default
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: supersecretpassword
      MYSQL_DATABASE: db
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: dbpassword

  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    volumes:
      - wordpress:/var/www/html
    networks:
      - traefik_default
      - default
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik_default"
      - "traefik.http.middlewares.yourdomain-https.redirectscheme.scheme=https"
      - "traefik.http.routers.yourdomain-http.entrypoints=web"
      - "traefik.http.routers.yourdomain-http.rule=Host(`yourdomain.com`, `www.yourdomain.com`)"
      - "traefik.http.routers.yourdomain-http.middlewares=yourdomain-https@docker"
      - "traefik.http.routers.yourdomain.middlewares=secureHeader@file"
      - "traefik.http.routers.yourdomain.entrypoints=websecure"
      - "traefik.http.routers.yourdomain.rule=Host(`yourdomain.com`, `www.yourdomain.com`)"
      - "traefik.http.routers.yourdomain.tls=true"
      - "traefik.http.routers.yourdomain.tls.options=default"
      - "traefik.http.routers.yourdomain.tls.certresolver=mytlschallenge"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: db
      WORDPRESS_DB_USER: dbuser
      WORDPRESS_DB_PASSWORD: dbpassword
networks:
  traefik_default:
    external: true
volumes:
  db-data: {}
  wordpress: {}

Note, I do stress, please god use your own passwords!

It is the docker labels that are the real magic here. The configuration will ensure redirection of HTTP to HTTPS, letsencrypt certificate with the www version as an alternate name and will apply the default super secure TLS config from the dynamic TOML file.

Not to mention that Traefik 2 also does routing of TCP as well using services. That however will be for another day.

https://docs.traefik.io

https://chriswiegman.com/2019/10/serving-your-docker-apps-with-https-and-traefik-2/

Docker WordPress & Traefik 2
Tagged on:                     

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.