Traefik Reverse Proxy with HTTPS¶
Intro¶
So, you have a Docker Swarm mode cluster set up as described in Docker Swarm still rocks?
With Traefik you can add a versatile Load balancer and reverse proxy with HTTPS, that can do a lot of things:
- Handle connections.
- Expose specific services and applications based on their domain names.
- Handle multiple domains (if you need to). Similar to "virtual hosts".
- Handle HTTPS.
- Acquire (generate) HTTPS certificates automatically (including renewals) with Let's Encrypt.
- Add HTTP Basic Auth for any service that you need to protect and doesn't have its own security, etc.
- Get all its configurations automatically from Docker labels set in your stacks (you don't need to update configuration files).
These ideas, techniques, and tools would also apply to other cluster orchestrators, like Kubernetes or Mesos, to add a main load balancer with HTTPS support, certificate generation, etc. But this article is focused on Docker Swarm mode. Traefik itself can be used as an ingress controller for Kubernetes, so you can apply the same ideas there similarly.
User Interface¶
The guide includes how to expose the internal Traefik web UI dashboard through the same Traefik load balancer, using a secure HTTPS certificate and HTTP Basic Auth.
How it works¶
The idea is to have a main load balancer/reverse proxy that covers all the Docker Swarm cluster and handles HTTPS certificates and requests for each domain.
But doing it in a way that allows you to have other Traefik services inside each stack without interfering with each other, to redirect based on path in the same stack (e.g. one container handles /
for a web frontend and another handles /api
for an API under the same domain), or to redirect from HTTP to HTTPS selectively.
Each stack dynamically configures Traefik, so that it can be deployed completely separated from the original (initial) Traefik stack.
Note
The setup we propose here, does not properly isolate stacks from each other, because each stack can arbitrarily configure any routes, i.e. one stack could "steal" the route from another. Security-wise we assume, that all stacks are "friendly". Because deploying a stack involves full remote control over the cluster anyway, this is a viable approach. For separating stacks you will want to use more advanced concepts (e.g. multiple Docker Swarm clusters, nested Kubernetes, Kubernetes with RBAC).
Concept¶
Without going into too much into detail(TM), this can be done by an overlay network in Docker Swarm that all nodes in the cluster are sharing and via the usage of the Traefik Docker provider, allowing for configuring Traefik via Docker Labels, that can be associated in a stack deployment file.
For any generic routing needs, the Traefik file provider is also used.
Preparation¶
- You need either a wildcard DNS entry, that points to the public IP of any of the Docker Swarm nodes, or a DNS entry, resolving to the public IP of any of the Docker Swarm nodes
nslookup traefik.on.dockerswarmstill.rocks nslookup whoami.on.dockerswarmstill.rocks
- Connect to a manager node in your cluster (you might have only one node)
- Create a network that will be shared with Traefik and the containers that should be accessible from the outside, with:
docker context use dockerswarmstillrocks
docker network create --driver=overlay traefik-public
- Create a password for a very (HTTP) basic auth, that can be used to secure the Traefik Dashboard
- Use
openssl
to generate the "hashed" version ofpassword-is-a-bad-password
and store it in an environment variable:
export ADMIN_HASHED_PASSWORD=$(openssl passwd -apr1 password-is-a-bad-password)
- Export a email address that will be used to register the HTTPS certificates with Let's Encrypt:
export DOMAIN_EMAIL=admin@example.org
Tip: Please use a valid email address, as Let's Encrypt will send you important information about your certificates to that email address.
- and now configure Traefik appropriately, its config consists of a
services.yml
, atraefik.yml
and adocker-compose.yml
-
You can download templates:
curl -L dockerswarmstill.rocks/stacks/traefik/services.yml -o services.yml curl -L dockerswarmstill.rocks/stacks/traefik/traefik.yml -o traefik.yml curl -L dockerswarmstill.rocks/stacks/traefik/docker-compose.yml -o docker-compose.yml curl -L dockerswarmstill.rocks/stacks/traefik/docker-compose.override.yml -o docker-compose.override.yml
-
Configure Traefik itself and either replace
$DOMAIN_EMAIL
with the email address you exported above:or stamp it into it with:api: insecure: true dashboard: true # debug: true log: level: ERROR accessLog: {} # uncomment to enable prometheus export # -> https://doc.traefik.io/traefik/observability/metrics/prometheus/ #metrics: # prometheus: {} entryPoints: http: address: :80 https: address: :443 certificatesResolvers: letsencrypt: acme: # this must be replaced via envsubst or similar # or use docker compose run --rm envsubst to stamp it email: $DOMAIN_EMAIL storage: /letsencrypt/acme.json tlsChallenge: {} # uncomment for testing against staging... # caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" providers: file: filename: /etc/traefik/services.yml watch: true docker: exposedByDefault: false swarmMode: true
docker context use desktop-linux docker compose run --rm envsubst
Tip: Your "local" Docker context might have a different name.
Tip: If you get an error message, make sure, that you have also downloaded the
docker-compose.override.yml
file, as it contains the envsubst helper service. -
Configure the file provider with its global middleware and the dashboard, either replace
$HASHED_PASSWORD
with the (hashed) password you generated above and replace$DOMAIN_TRAEFIK_DASHBOARD
with the domain you want to use for the Traefik Dashboard (e.g.traefik.on.dockerswarmstill.rocks
):or export and stamp it (locally) into it with:http: # define global routers, that use global services # see -> https://doc.traefik.io/traefik/reference/install-configuration/providers/others/file/ routers: traefik-dashboard: rule: "Host(`$DOMAIN_TRAEFIK_DASHBOARD`)" service: "dashboard@internal" entryPoints: - http - https middlewares: - admin-basic-auth - https-redirect tls: certResolver: letsencrypt traefik-api: rule: "Host(`$DOMAIN_TRAEFIK_DASHBOARD`) && PathPrefix(`/api`)" service: "api@internal" entryPoints: - http - https middlewares: - admin-basic-auth - https-redirect tls: certResolver: letsencrypt # global middlewares, that can be re-used middlewares: https-redirect: redirectScheme: scheme: https permanent: true admin-basic-auth: basicAuth: users: # this must be replaced via envsubst or similar # or use docker compose run --rm envsubst to stamp it - "admin:$ADMIN_HASHED_PASSWORD" # global routers use global services # services:
export DOMAIN_TRAEFIK_DASHBOARD=traefik.on.dockerswarmstill.rocks docker compose run --rm envsubst
-
and finally configure the Traefik stack itself, either replace
$DOMAIN_WHOAMI
with the domain you want to use for the whoami service (e.g.whoami.on.dockerswarmstill.rocks
):or export it as an environment variable:configs: services-yml: file: ./services.yml traefik-yml: file: ./traefik.yml services: reverse-proxy: image: traefik:v2.4 configs: - source: services-yml target: /etc/traefik/services.yml - source: traefik-yml target: /etc/traefik/traefik.yml deploy: placement: constraints: - node.role == manager ports: # The HTTP port - "80:80" # HTTPS also - "443:443" # publish The Web UI (enabled by --api.insecure=true) # on a public port, uncomment to be able to reach it without DNS # but only for testing purposes! # - "8080:8080" volumes: # Add Docker socket as a mounted volume, so that Traefik can read the labels of other services - /var/run/docker.sock:/var/run/docker.sock:ro # acme.json for le challenges - acme-data:/letsencrypt networks: - traefik-public # comment the following if you do not want to use the example whoami service whoami: # A container that exposes an API to show its IP address image: traefik/whoami:latest deploy: labels: - "traefik.enable=true" - "traefik.docker.network=traefik-public" # explicit http entrypoint - "traefik.http.routers.whoami-http.rule=Host(`${DOMAIN_WHOAMI}`)" - "traefik.http.routers.whoami-http.entrypoints=http" - "traefik.http.routers.whoami-http.service=whoamiService" - "traefik.http.services.whoamiService.loadbalancer.server.port=80" # https entrypoint - "traefik.http.routers.whoami-https.rule=Host(`${DOMAIN_WHOAMI}`)" - "traefik.http.routers.whoami-https.entrypoints=https" - "traefik.http.routers.whoami-https.tls=true" # explicitly use le as certresolver - "traefik.http.routers.whoami-https.tls.certresolver=letsencrypt" - "traefik.http.routers.whoami-https.service=whoamiHttpsService" - "traefik.http.services.whoamiHttpsService.loadbalancer.server.port=80" networks: - traefik-public volumes: acme-data: networks: # N.B. this must be created once on a manager node (e.g. docker network create --driver=overlay traefik-public) traefik-public: external: true
export DOMAIN_WHOAMI=whoami.on.dockerswarmstill.rocks
Tip: The whoami service is merely for demonstrating proper usage of Traefik, if you do not want to deploy it anymore, simply remove or comment out the
whoami
service in thedocker-compose.yml
file.
Tip
Read the internal comments to learn what each configuration is for.
Deploy it¶
Deploy the stack with:
docker context use dockerswarmstillrocks
docker stack deploy -c docker-compose.yml traefik
Tip: Make sure, you are controlling your Docker Swarm manager.
Check it¶
- Check if the stack was deployed with:
docker stack ps traefik
It will output something like:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
jq7anezva94o traefik_reverse-proxy.1 traefik:v2.4 dockerswarmstillrocks-4gb-nbg1-1 Running Running 31 minutes ago
zm4oaujqcvn7 traefik_whoami.1 traefik/whoami:latest dockerswarmstillrocks-4gb-nbg1-1 Running Running 31 minutes ago
- You can check the Traefik logs with:
docker service logs traefik_reverse-proxy
Check the user interface¶
After some seconds/minutes, Traefik will acquire the HTTPS certificates for the web user interface (UI).
You will be able to securely access the web UI at https://$DOMAIN_TRAEFIK_DASHBOARD
(e.g. https://traefik.on.dockerswarmstill.rocks) using the username admin
and the created password (a pity password-is-a-bad-password
does not work, isn't it?).
Once you deploy a stack, you will be able to see it there and see how the different hosts and paths map to different Docker services / containers.
If you have also deployed the whoami
service, you will be able to access it at https://$DOMAIN_WHOAMI
(e.g. https://whoami.on.dockerswarmstill.rocks).
Advanced usage¶
Getting the client IP¶
If you need to read the client IP in your applications/stacks using the X-Forwarded-For
or X-Real-IP
headers provided by Traefik, you need to make Traefik listen directly, not through Docker Swarm mode, even while being deployed with Docker Swarm mode.
For that, you need to publish the ports using "host" mode.
So, the Docker Compose lines:
ports:
- 80:80
- 443:443
need to be:
ports:
- target: 80
published: 80
mode: host
- target: 443
published: 443
mode: host