Docker - Traefik - Wildcard Cert

Page content

Intro

TLS is must, but do you wanna generate a own Certificate for each Service you Provide ? Specially, when you have a *.domain.tld Record set ?

Trafik is able to handle that for you. Let’s Encrypt offers the possibility to use DNS Validation for Wildcard Domains. Here is a list of Providers that can automate DNS Verfication.

Helpful URL

Fully Example with Docker Compose, Traefik, Digital Ocean

Prepare Env

cd /where/ever/you/want
mkdir data
touch data/acme.json

Variables

we need a few Variables. Let’s put them in a .env file and docker-compose will use them when called.

  • DOMAIN: the domain where you wanna provide/test the service
  • LE_DNS_EMAIL: for registration with let’s encrypt. must be a valid email address
  • DO_AUTH_TOKEN: the API token you generated at DO (https://cloud.digitalocean.com/account/api/tokens)
  • AUTH_USER: Username & Password for Authentication on the Website/Services you generate)
cat << 'EOF' > .env
DOMAIN="yourdomain.tld"
LE_DNS_EMAIL="[email protected]"
DO_AUTH_TOKEN=dop_v1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
EOF

AUTH_USER=$(echo $(htpasswd -nb CHANGEUSER CHANGEPASSWORD) | sed -e s/\\$/\\$\\$/g)
echo "AUTH_USER=${AUTH_USER}" >> .env

traefik.yml

cat << 'EOF' > data/traefik.yml
log:
  # DEBUG, PANIC, FATAL, ERROR, WARN, and INFO.
  level: DEBUG

api:
  dashboard: true

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    watch: true 
  file:
    filename: "/dynamic_conf.yml"
    watch: true 

certificatesResolvers:
  dns:
    acme:
      email: ${LE_DNS_EMAIL}
      storage: acme.json
      keyType: RSA4096
      # Staging
      #caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      # Production 
      caServer: https://acme-v02.api.letsencrypt.org/directory
      dnschallenge: 
        provider: digitalocean
        delaybeforecheck: 60
        resolvers: 
        - ns1.digitalocean.com:53
        - ns2.digitalocean.com:53
        - ns3.digitalocean.com:53
EOF

dynamic_conf.yml

cat << 'EOF' > data/dynamic_conf.yml
tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true

http:
  middlewares:    
    # [email protected]
    # - Set Sameorigin
    # - Set HSTS
    # - enforce HTTPS
    secHeaders:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        customFrameOptionsValue: "SAMEORIGIN"
        sslRedirect: true
        #HSTS Configuration
        #stsIncludeSubdomains: true
        #stsPreload: true
        #stsSeconds: 15552000
            
    # [email protected]: Redirect HTTP -> HTTPS
    redirect:
      redirectScheme:
        scheme: "https"
EOF

docker-compose

cat << 'EOF' > docker-compose.yml 
version: '3'

services:

  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true

    networks:
      - proxy

    ports:
      - 80:80
      - 443:443

    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/acme.json:/acme.json
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/dynamic_conf.yml:/dynamic_conf.yml

    environment:
      - DO_AUTH_TOKEN=${DO_AUTH_TOKEN}

    command:
      - --certificatesresolvers.le.acme.dnschallenge=true
      - --certificatesresolvers.le.acme.dnschallenge.provider=digitalocean
      - --certificatesresolvers.le.acme.email=${LE_DNS_EMAIL}
      - --certificatesresolvers.le.acme.storage=/acme.json

    labels:
      - "traefik.enable=true"

      # – ------------------------------------------------------
      # INCLUDE: dynamic_conf.yml
      # - [email protected]
      # - [email protected]
      - "providers.file.filename=/dynamic_conf.yml"

      # – ------------------------------------------------------
      # Middleware: traefik-auth
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${AUTH_USER}"

      # – ------------------------------------------------------
      # Router "traefik": http://traefik.nodnsapi.com -> Redirect     
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"      
      - "[email protected]"

      # – ---------------------------------------------------- –      
      # Router "traefiks": https://traefik.nodnsapi.com -> Traefik Dashboard
      - "traefik.http.routers.traefiks.entrypoints=https"
      - "traefik.http.routers.traefiks.middlewares=traefik-auth,[email protected]"
      - "traefik.http.routers.traefiks.rule=Host(`traefik.${DOMAIN}`)"
      - "[email protected]"
      - "traefik.http.routers.traefiks.tls=true"      

      # [email protected]~S ------------------------------------------------------
      # Router "whoami-secure": To get Wildcard-Certs, no Service
      - "[email protected]"
      - "traefik.http.routers.whoami-secure.tls.certResolver=dns"
      - "traefik.http.routers.whoami-secure.tls.domains[0].main=*.${DOMAIN}"
      # [email protected]~S ------------------------------------------------------

  dozzle:
    image: amir20/dozzle:latest
    networks:
      - proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data/acme.json:/acme.json
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/dynamic_conf.yml:/dynamic_conf.yml
    labels:
      - "traefik.enable=true"
      # Redirect to HTTPS
      - "traefik.http.routers.dozzle.entrypoints=http"
      - "traefik.http.routers.dozzle.rule=Host(`dozzle.${DOMAIN}`)"      
      - "[email protected]"
      # Real Service
      - "traefik.http.routers.dozzles.entrypoints=https"
      - "traefik.http.routers.dozzles.middlewares=traefik-auth"
      - "traefik.http.routers.dozzles.rule=Host(`dozzle.${DOMAIN}`)"
      - "traefik.http.routers.dozzles.tls.certresolver=dns"
      - "traefik.http.routers.dozzles.tls=true"      

  whoami:
    image: "traefik/whoami"
    scale: 3
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      # Redirect to HTTPS
      - "traefik.http.routers.whoami.entrypoints=http"
      - "traefik.http.routers.whoami.rule=Host(`whoami.${DOMAIN}`)"      
      - "[email protected]"
      # Real Service
      - "traefik.http.routers.whoamis.entrypoints=https"
      - "traefik.http.routers.whoamis.middlewares=traefik-auth"
      - "traefik.http.routers.whoamis.rule=Host(`whoami.${DOMAIN}`)"
      - "traefik.http.routers.whoamis.tls.certResolver=dns"
      - "traefik.http.routers.whoamis.tls=true"

networks:
  proxy:
    external: true
EOF

finally, we need to create the network

docker network create proxy

and we are ready to go.

Check Config

you can build the config and confirm that your variables from the .env file got applied

docker compose convert

docker compose up

docker compose up

Browse to

sha256: 623236f3c227b30d0355adb7a990520536fa67eb48ff77d0262336da9822e5a4