Skip to main content

Traefik

Traefik is an HTTP reverse proxy that automatically gets its configuration from the running Docker services (or containers).

This example uses another container for proxying the Docker socket. This way, if the Traefik container is compromised by an attacker, the blast radius is limited.

The dns01-worker-api-token secret needs to be configured manually outside of the stack (e.g. in the Portainer UI). It should be the value specified in the Cloudflare worker that sets up the DNS records.

Swarm mode

I recommend to run your Docker host in swarm mode. If your swarm consists of only a single host, I recommend to run Traefik in non-swarm mode. That is, do not set --providers.docker.swarmMode=true.

When Traefik runs in swarm mode, it cannot automatically detect the port that your container listens on (see Traefik docs). You would have to specify an extra label on each container to tell Traefik what port it should direct the requests to, so it's one line more to write.
When Traefik runs in non-swarm mode, it can detect the port of a container automatically (e.g. when the container uses EXPOSE in its Dockerfile).

If your swarm consists of more than one host, you might need to run Traefik in swarm mode as well (i.e. with --providers.docker.swarmMode=true), in order to connect to containers running on other hosts.

Stack file

stack.yml
version: '3.3'

services:
traefik:
image: 'traefik:v2.10'
read_only: true
networks:
- traefik
- socket_proxy
command:
# - '--log.level=DEBUG'

- '--providers.docker=true'
- '--providers.docker.endpoint=tcp://docker-socket-proxy:2375'
- '--providers.docker.network=traefik'

- '--entrypoints.web.address=:80'
- '--entrypoints.web.http.redirections.entrypoint.to=websecure'

- '--entrypoints.websecure.address=:443'

# These defaults are applied to any router that doesn't override them
- '--entrypoints.websecure.http.tls.certResolver=dnsresolver'
- '--entrypoints.websecure.http.tls.domains[0].main=connorlanigan.com'
- '--entrypoints.websecure.http.tls.domains[0].sans=*.connorlanigan.com'

- '--certificatesresolvers.dnsresolver.acme.dnschallenge=true'
- '--certificatesresolvers.dnsresolver.acme.dnschallenge.provider=httpreq'
- '--certificatesresolvers.dnsresolver.acme.email=letsencrypt@connorlanigan.com'
- '--certificatesresolvers.dnsresolver.acme.storage=/letsencrypt/acme.json'

# To be replaced when I have migrated all services to dnsresolver. I just wanted a nicer name for the resolver
- '--certificatesresolvers.myresolver.acme.dnschallenge=true'
- '--certificatesresolvers.myresolver.acme.dnschallenge.provider=httpreq'
- '--certificatesresolvers.myresolver.acme.email=contact@connorlanigan.com'
- '--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json'

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

# For TLS challenge instead of DNS
#- "--certificatesresolvers.tlsresolver.acme.tlschallenge=true"
#- "--certificatesresolvers.tlsresolver.acme.email=contact@connorlanigan.com"
#- "--certificatesresolvers.tlsresolver.acme.storage=/letsencrypt/acme.json"

- '--serversTransport.forwardingTimeouts.dialTimeout=10s'
- '--serversTransport.insecureSkipVerify=true'

# Metrics (optional)
- '--metrics.influxdb2=true'
- '--metrics.influxdb2.address=https://eu-central-1-1.aws.cloud2.influxdata.com'
- '--metrics.influxdb2.token=<Influx API token>'
- '--metrics.influxdb2.org=<Influx org name>'
- '--metrics.influxdb2.bucket=<Influx bucket name>'
- '--metrics.influxdb2.addEntryPointsLabels=false'
- '--metrics.influxdb2.addRoutersLabels=true'
- '--metrics.influxdb2.addServicesLabels=false'
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
environment:
- 'HTTPREQ_ENDPOINT=https://dns01.example.workers.dev'
- 'HTTPREQ_USERNAME=user'
- 'HTTPREQ_PASSWORD_FILE=/run/secrets/dns01-worker-api-token'
volumes:
- 'letsencrypt:/letsencrypt'
secrets:
- 'dns01-worker-api-token'
cap_drop:
- ALL
deploy:
labels:
shepherd_autoupdate:

docker-socket-proxy:
#read_only: true
environment:
CONTAINERS: 1
image: tecnativa/docker-socket-proxy
cap_drop:
- ALL
networks:
- socket_proxy
ports:
- 2375
tmpfs:
- /run:uid=0,gid=0
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'

volumes:
letsencrypt:

secrets:
dns01-worker-api-token:
external: true

networks:
traefik:
external: true
socket_proxy: