Skip to content

traefik

The traefik role runs Traefik and a read-only Docker socket proxy as one compose project. Containers join the shared network and opt in with traefik.enable=true. Traefik never sees the Docker socket itself; it talks to the proxy, which allows only reads of containers and networks.

  • Creates the project directory (traefik_dir, /opt/traefik) with a 0700 acme directory for certificates.
  • Writes the DNS provider environment to dns.env with mode 0600, and never logs its contents.
  • Writes the static configuration, the compose file, and the files you give in traefik_dynamic_files for the file provider.
  • Starts the project with Docker Compose. A change of the static configuration or the DNS environment restarts Traefik.
  • Creates a shared network (proxy, 172.31.255.0/24) and gives Traefik a fixed address on it (172.31.255.254), outside the range Docker assigns from, so a backend can trust exactly that address.

Certificates come from ACME. By default the role uses the DNS-01 challenge, which allows wildcard certificates and needs no port open to the internet. With traefik_acme_challenge: http it uses HTTP-01 instead; see HTTP-01 certificates.

traefik_entrypoints is a list of dicts with these keys:

KeyMeaning
nameEntrypoint name.
portContainer port, and host port unless host_port is set.
host_portPort published on the host (default: port). Lets two entrypoints share a host port on different bind addresses. A redirect to such an entrypoint goes to its host_port.
bindHost addresses to publish on. Empty publishes on all.
protocoltcp (default) or udp.
tlsTerminate TLS with the certificate resolver.
as_defaultRouters without an explicit entrypoint use this one.
redirect_toRedirect HTTP on this entrypoint to another entrypoint.
middlewaresMiddlewares every router on this entrypoint gets, for example [name@file]. Not allowed together with redirect_to.

The default is web on port 80, redirecting to websecure on port 443, which terminates TLS and is the default.

VariableDefaultMeaning
traefik_imagepinned traefik v3 tagTraefik image.
traefik_acme_emailemptyACME account e-mail; required when an entrypoint has tls on.
traefik_acme_ca_serverLet’s Encrypt productionACME directory. Use the staging directory for tests.
traefik_acme_challengednsdns (DNS-01) or http (HTTP-01). Also the name of the cert resolver.
traefik_acme_http_entrypointwebHTTP-01 only. Entrypoint that answers the challenge; it must listen on host port 80.
traefik_dns_providerhetznerlego DNS provider of the DNS-01 challenge.
traefik_dns_env{}Environment of the DNS provider, usually an API token.
traefik_cert_domains[]Certificates to request: main and sans.
traefik_networkproxyShared network name that other roles join.
traefik_access_logfalseWrite an access log to stdout.
traefik_docker_allow_empty_servicesfalseKeep the routers of containers without a healthy server instead of letting them disappear. See Error pages.
traefik_error_pages_enabledfalseRoute errors to an error pages service. See Error pages.
traefik_dynamic_files{}File-provider files, name to YAML content.

All variables: traefik in the reference.

- hosts: edge
become: true
roles:
- noixdev.infra.docker
- role: noixdev.infra.traefik
vars:
traefik_acme_email: ops@example.com
traefik_dns_provider: hetzner
traefik_dns_env:
HETZNER_API_TOKEN: "{{ vault_dns_token }}"
traefik_cert_domains:
- main: example.com
sans: ["*.example.com"]
traefik_dynamic_files:
headers.yml:
http:
middlewares:
secure-headers:
headers:
stsSeconds: 31536000

Another container uses Traefik by joining the proxy network and setting labels such as traefik.enable=true and a router rule. The portainer and pulsix_manager roles do this for you when you set their *_host variable.

With traefik_acme_challenge: http Traefik proves control of a name by answering a request on port 80, so no DNS API token is needed. Traefik requests one certificate per router host, when a router first uses the name. The resolver is named after the challenge: http here, dns by default.

- hosts: edge
become: true
roles:
- noixdev.infra.docker
- role: noixdev.infra.traefik
vars:
traefik_acme_email: ops@example.com
traefik_acme_challenge: http
traefik_acme_http_entrypoint: web

The default entrypoints already fit: web listens on host port 80 and redirects to websecure.

Requirements, which the role checks before it changes anything:

  • traefik_acme_http_entrypoint names an existing entrypoint whose host port is 80 (host_port, or port when host_port is not set).
  • traefik_cert_domains contains no wildcard. HTTP-01 cannot issue them; use the DNS-01 challenge for wildcards.

Each host must also resolve publicly to this server, and port 80 must be reachable from the internet. An entrypoint on a private address cannot answer the challenge.

With traefik_error_pages_enabled: true the role routes errors to an error pages service that serves /<code>.html. By default it also runs that service: the error-pages container (traefik_error_pages_image, default noixdev/error-pages:1) as the compose service error-pages in the Traefik project, with the alias error-pages on the shared network. It runs read-only as user 65532, without capabilities, with the image’s healthcheck, CPU and memory limits (traefik_error_pages_cpus, traefik_error_pages_memory) and a volume on /data for maintenance mode.

The container’s environment is traefik_error_pages_env. It replaces the defaults as a whole (theme glass, languages en,de), so list every variable you want. See the error-pages configuration for the options.

To run the service somewhere else, set traefik_error_pages_container: false and point traefik_error_pages_url at it. The default URL is the bundled container, http://error-pages:8080.

The role writes dynamic/error-pages.yml for the file provider with:

  • the middleware error-pages-errors, an errors middleware that asks the service for /{status}.html for every status in traefik_error_pages_status (default ["502-504"]);
  • the middleware error-pages-compress, limited to traefik_error_pages_compress_types, unless traefik_error_pages_compress is false. Each page is 100-200 KB, so keep it on;
  • a router error-pages-<name> for each entry of traefik_error_pages_routes, answering with one fixed page through a replacePath middleware, so a visitor cannot pick another page by path;
  • the service error-pages.

Every router on the entrypoints listed in traefik_error_pages_entrypoints gets error-pages-compress and error-pages-errors, before the entrypoint’s own middlewares. Those entrypoints must exist and must not redirect.

Each route has these keys:

KeyMeaning
nameRequired. [a-z0-9-]+, unique.
entrypointsRequired. Entrypoints the router listens on.
ruleRouter rule. Default HostRegexp(.+).
priorityHigher wins. Default 1.
pageA status code or internal. Default 404. The page is requested as /<page>.html.

A catch-all route with the lowest priority answers unknown hosts with the 404 page. A route with page: internal and a higher priority is a guard: it answers a name that must not be served on an entrypoint with the “internal only” page.

- hosts: edge
become: true
roles:
- noixdev.infra.docker
- role: noixdev.infra.traefik
vars:
traefik_acme_email: ops@example.com
traefik_dns_env:
HETZNER_API_TOKEN: "{{ vault_dns_token }}"
traefik_cert_domains:
- main: example.com
sans: ["*.example.com"]
traefik_entrypoints:
- name: web
port: 80
redirect_to: websecure
- name: websecure
port: 443
bind: [192.0.2.10]
tls: true
as_default: true
- name: internal
port: 8443
host_port: 443
bind: [198.51.100.10]
tls: true
traefik_docker_allow_empty_services: true
traefik_error_pages_enabled: true
traefik_error_pages_entrypoints: [websecure, internal]
traefik_error_pages_routes:
- name: catchall
entrypoints: [websecure, internal]
- name: admin-guard
entrypoints: [websecure]
rule: Host(`admin.example.com`)
priority: 100
page: internal

Here websecure is public and internal shares host port 443 on a private address. The guard answers admin.example.com on the public entrypoint with the internal-only page, and the catch-all answers every other unknown host with 404.

With traefik_docker_allow_empty_services: true a container without a healthy server keeps its router, so a request is not handled by the catch-all and answered with a misleading 404. The role’s variable description says the router answers 503. With Traefik 3.7 it answers 500 instead, and the default 502-504 status list does not catch that. You can add 500 to traefik_error_pages_status, but then every 500 from every application is replaced by the error page and the application’s own error body is lost. Add it only if you accept that. See also the error-pages Traefik guide.

The container’s volume holds the maintenance flag. Switch it with the container’s own command. The container name is <compose project>-error-pages-1, so traefik-error-pages-1 with the default traefik_project:

Terminal window
docker exec traefik-error-pages-1 /error-pages maintenance on
docker exec traefik-error-pages-1 /error-pages maintenance off
docker exec traefik-error-pages-1 /error-pages maintenance status

Set traefik_error_pages_enabled: false (the default). The role removes dynamic/error-pages.yml after Traefik has restarted without the middlewares, and stops writing the container. Remove the entrypoint middlewares and any router labels of your own that refer to error-pages-*@file.

  • Traefik needs Docker on the host. Apply docker first.
  • traefik_acme_email is required with TLS. The role stops with a message when an entrypoint has tls: true and the e-mail is empty.
  • Pick the DNS provider and its variables together (DNS-01 only). traefik_dns_provider is a lego provider code, and traefik_dns_env holds that provider’s variable names. See the lego documentation for them. Keep the values in a vault.
  • Test with the ACME staging directory first. Let’s Encrypt rate-limits failed validations on the production directory.
  • traefik_network_range must not contain traefik_address. The fixed address is deliberately outside the pool Docker assigns from.
  • Access logs go to stdout. For CrowdSec to read them they must be written to a file on the host; see crowdsec.
  • The MTU of the compose networks is traefik_mtu. Match it to docker_mtu on networks that need less than 1500.
  • error-pages.yml is reserved. traefik_dynamic_files cannot contain a file of that name while error pages are enabled.