Traefik With Let's Encrypt Wildcard SSL Certificate Using Docker Compose

Let’s encrypt has introduced wildcard certificates and traefik has released a v2 which is completely different from v1. This calls for a tutorial on how to use the two together using docker compose.

Why wildcard certificates?

You don’t need separate https certicates for your subdomain, especially if you are used to deploying your applications as different subdomains. This will significantly reduce calls to Let’s Encrypt servers which is now important since they have introduced serious rate limits. And of course, Let’s encrypt is totally free so you have no reason to not use HTTPS anymore, not to mention its more secure than using HTTP.

Why traefik

Traefik is a beautiful piece of software which is container aware and can get/issue/renew Let’s Encrypt certificates automatically, once configured.

How?

At the time of writing this, Let’s Encrypt only supports wildcard certificates using the DNS-01 verification method so thats what this article uses as well.

This article also uses duckdns.org for free/dynamic domains. Traefik supports other DNS providers, any of which can be used instead.

Docker compose file for Traefik:

version: "3.3"

services:
  traefik:
    image: "traefik:v2.5"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls.domains[0].main=<your.domain.com>"
      - "--entrypoints.websecure.http.tls.domains[0].sans=*.<your.domain.com>"
      - "--entrypoints.websecure.http.tls.certresolver=myresolver"
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=duckdns"
      - "--certificatesresolvers.myresolver.acme.email=<your email address>"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    environment:
      - "DUCKDNS_TOKEN=<token>"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik-dashboard.your.domain.com`)"
      - "traefik.http.routers.traefik-dashboard.service=traefik-dashboard"
      - "traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080"

The above config also exposes the traefik dashboard as traefik-dashboard.your.domain.com

Note: Uncomment/add the following in command when testing: To get debug logs:

--log.level=DEBUG

To use the staging lets encrypt server as debugging on production servers will get you rate limited

- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"

Deploy each application in a separate docker-compose file

I prefer using different docker-compose.yml files for different applications. It makes managing them easier, especially when you have a lot of applications.

The following is an example docker-compose file for an application, that I use:

---
version: "3"
services:
  heimdall:
    image: ghcr.io/linuxserver/heimdall
    container_name: heimdall
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
    volumes:
      - ./config:/config
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.heimdall.rule=Host(`heimdall.your.domain.com`, `www.your.domain.com`)"
      - "traefik.http.routers.heimdall.service=heimdall"
      - "traefik.http.services.heimdall.loadbalancer.server.port=80"
    networks:
      - traefik_default
    restart: unless-stopped

networks:
  traefik_default:
    external: true

Please note the networks sections (two) in the above template/example. Since traefik and the applications are in different docker compose files, they will endup in different networks by default and will not be able to communicate with each other. The networks sections add the application containers to traefik’s network as well so that they can communicate with each other. Also note that there are no ports forwarded to the host except for 80 (http) and 443 (https) which traefik runs on.

Deploy applications in the same docker-compose file as that of traefik

You can put all the applications as different services in a single docker-compose file along with the traefik service. This can be use full if there are only a few applications or if the applications are dependent on each other for some reason or simply because its simpler.

If that’s the route you are taking, note that the networks sections (both) are not required since all the services in a docker-compose file will end up in the same network by default. Except for the lack of networks sections, the service definitions are exactly the same.