Post

Secure everything using Traefik, Let's Encrypt, and Docker

Install and configure a Traefik reverse proxy while also auto-generating a wildcard SSL certificate using Cloudflare and Let’s Encrypt.

Code here: https://github.com/nateleavitt/traefik-docker-ssl

Description

This is going to setup and use Traefik as a reverse proxy for all your other services (containers). SSL termination will happen on Traefik, which will then forward the traffic to the requested service. Certificates will be auto-generated with Let’s Encrypt, with Cloudfront as the DNS verification provider.

Prereqs

  • You need a cloudflare account
  • You need a registered domain which is also using the cloudflare nameservers
  • (suggested) Internal DNS

Setup and Config

Cloudflare setup

First you need to get a token or api key from Cloudflare. I would suggest a token. Once logged in, click on your profile, and select My Profile > API Tokens > Create Token. Make sure you give it the ability to Edit the Dns zone, and also I add SSL & Certificates. Once you get the token, make sure to copy it somewhere safe.

Env setup

I have set this project up to use a .env file for secrets. You can change this, as it isn’t the most secure way of handling secrets. But to do this, rename the .env.example file to .env and add your token.

1
2
3
4
5
CF_DNS_API_TOKEN=your-token-here
# If you chose to use an email and api key comment the line above and
# uncomment the 2 lines below. I would suggest setting up a token.
# [email protected]
# CF_API_KEY=your-api-key-here

Traefik Dashboard User Setup

In order to access the traefik dashboard, you will need to setup a username and password. Make sure to replace <USER> and <PASSWORD> with your own made up values. This can be done using the following:

1
echo $(htpasswd -nb "<USER>" "<PASSWORD>") | sed -e s/\\$/\\$\\$/g

Copy the output into the docker-compose.yml file in the section traefik.http.middlewares.traefik-auth.basicauth.users=your-user:your-hashed-password. Replace your-user and your-hashed-password with the values you created above.

Traefik Config

This compose file starts traefik, it’s dashboard, and also configures your wildcard domain settings. Some things to note in this file; First, you’ll need to update the labels to include your domain. Notice I use .local on all my domains. This is because I like to use the local subdomain of my domain for all internal services, which then makes it easy to refer to my internal services using DNS entries. Also, take note that I am creating the proxy docker network to isolate it’s network.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

    env_file: .env
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
      - ./config:/config/:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(`dashboard.local.your-domain.com`)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=your-user:your-hashed-password"
      - "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(`dashboard-host.local.your-domain.com`)"
      - "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=local.your-domain.com"
      - "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.local.your-domain.com"
      - "traefik.http.routers.traefik-secure.service=api@internal"

networks:
  proxy:
    external: true

In the data/ folder you will see some traefik config. You’ll need to update your cloudflare email below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# data/traefik.yml
api:
  dashboard: true
  debug: true
entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"
serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: "/config/"
    watch: true
certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected] # your cloudflare email
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        disablePropagationCheck: true
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"
log:
  level: DEBUG

Once you have configured and updated your settings, I would start traefik and watch the logs to see if the SSL certificates are being created correctly. Also pay attention to any logs/errors from Cloudflare. If you do get errors, make sure your token is providing all the necessary permissions for your DNS zone on Cloudflare.

1
docker compose up

Try logging into the dashboard by going to the url you used in the docker-compose.yml label above. The label I use is dashboard.local.your-domain.com. Also, you will use the username and password you configured in the User Setup section. If you can login, then things are going great up to this point, and you can move on to the next section.

Service Config

I have set this up to where you put each of your services/router denfinitions into the config folder. Traefik will watch this folder and any time services (yml files) are added to that folder Traefik will automatically update. Below is an example service which will serve the https://hello-world.your-domain.com address.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# hello-world.yml
http:
#routers
  routers:
    hello-world:
      entryPoints:
        - "https"
      rule: "Host(`hello-world.your-domain.com`)"
      middlewares:
        - default-headers
        - https-redirectscheme
      tls: {}
      service: hello-world
#endrouter

#service
  services:
    hello-world:
      loadBalancer:
        servers:
          - url: "http://server-ip-address:port"
        passHostHeader: true
#endservice

Make sure to included any middleware specific to your service. For example, in the default.yml file I have included some commonly used middlewares; https-redirectscheme, default-headers, and default-whitelist. In my example above you’ll see that default-headers and https-redirectscheme is being used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# default.yml
http:
  middlewares:
    https-redirectscheme:
      redirectScheme:
        scheme: https
        permanent: true

    default-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https

    default-whitelist:
      ipWhiteList:
        sourceRange:
        - "10.0.0.0/8"
        - "192.168.0.0/16"
        - "172.0.0.0/12"

    secured:
      chain:
        middlewares:
        - default-whitelist
        - default-headers

From there feel free to add/remove services to the config folder, which will have an SSL certificate generated and then provisioned by Traefik.

This post is licensed under CC BY 4.0 by the author.