About

This tutorial is an explanation of an all-in-one template for the following technologies:

  • docker,
  • traefik,
  • authelia,
  • duckdns

The all-in-one template achieves the effect of reverse-proxying docker containers using traefik on-the-fly by automatically detecting the exposed ports, providing automatic TLS certificate registration as well as dynamic DNS through DuckDNS and finally additionally implementing authentication for all containers behind traefik.

Even though this sounds fancy, it is pretty much the minimal requirement for a dynamic road-runner configuration of a media box that automatically manages itself without user intervention such that even if the configuration looks complicated its functionality is fairly boilerplate and hence distributed as a configuration template.

This template is a superset of the mechanism implemented by the complete Servarr stack but with the actual stack omitted and only the configurations for traefik and authelia provided. Compared to the complete stack, this configuration will additionally automatically register a DNS name, set up certificates automatically and also perform authentication in order to protect backend services without authentication.

Ad reductio, this setup is the very same as using caddy which is something that has been previously accomplished but traefik offers a better integration with Docker given its automatic labeling and hostname registration services.

Architecture

The overall diagram is similar to the following layout where a request arrives from the Internet, gets processed by traefik and, if necessary, redirected to authelia for authentication; after authentication, the user is passed-through to the backends.

Configuration Parameters

There are several parameters that appear in the following configuration files and they must be instantiated to their real counterparts:

  • DUCKDNS_TOKEN - this is the token obtained after logging on to duckdns.org
  • DUCKDNS_DOMAIN - corresponds to the registered FQDN DuckDNS domain, ie: reg.duckdns.org
  • DUCKDNS_EMAIL - should be filled in with the E-Mail used when registering on DuckDNS
  • AUTH_DUCKDNS_DOMAIN - is a subdomain that must be chosen and dedicated to authentication, for example auth.duckdns.org,
  • REDIRECT_DUCKDNS_DOMAIN - a DuckDNS subdomain to redirect to by default after having authenticated with authelia. This is not obligatory, authelia already knows the origin of the request, for example, sonarr.reg.duckdns.org, after which is redirects to AUTH_DUCKDNS_DOMAIN asking the user to authenticate and in the end authelia will redirect back to sonarr.reg.duckdns.org such that REDIRECT_DUCKDNS_DOMAIN is optional and perhaps best suited if you want to run an organizer like Heimdal or similar.

The rest of the parameters can be left as-is whilst minding the Docker volume mount paths that might be different.

Traefik

The entire traefik configuration takes place within the compose or service file used to start traefik without any additional configuration files needed. Traefik will be responsible for updating DuckDNS with the sub-sub-domains created by the Servarr stack, that is and for example, it will register and correctly redirect sonarr.duckdns.org to the Sonarr instance running on the same entertainment network.

Here is some commentary on the traefik configuration:

  • traefik expects to be running within the same network as all the other containers which is hardcoded as entertainment - this is actually the network that is defined by the complete Servarr stack and it is useful because it allows traefik to auto-discover and label containers automatically without user intervetion. That is, any container that is supposed to be given a domain name on the network must just define its name, for example –name=sonarr and set its network to entertainment, for example –net=entertainment. After that traefik will automatically create sonarr.DUCKDNS_DOMAIN available and connections will be automatically reverse-proxied to Sonarr. If an example configuration for Sonarr, or other Servarrs is desired, the complete Servarr stack is a good starting point because those services were made to work with the configuration displayed on this page (the hostname registration is realized via the traefik command parameter --providers.docker.defaultrule='Host(`{{ normalize .Name}}.DUCKDNS_DOMAIN`)' defined in the compose, respectively service file).
  • traefik will automatically redirect from HTTP to HTTPs via the stanzas:
                --entrypoints.http.http.redirections.entrypoint.to='https'
                --entrypoints.http.http.redirections.entrypoint.scheme='https'
  • Automatic TLS certificate registration is performed via the duckdns DNS challenge in order to avoid having to open up a port for TLS verification (--certificatesresolvers.duckdns.acme.dnschallenge.provider='duckdns') with a permanent cache directory that should be volume-mounted on the host or certificates will be lost upon every traefik restart (--certificatesresolvers.duckdns.acme.storage='/letsencrypt/acme.json'). Conventionally, it is a good idea to not provide any alternate DNS servers and just use the ones conferred by the ISP but since we're going for a "cloud" setup with DuckDNS and automatic certificate registration it is safer to use the Google DNS servers in case local ISPs have weird setups (--certificatesresolvers.duckdns.acme.dnschallenge.resolvers='8.8.8.8,8.8.4.4') especially in these troublesome times.
  • Using DuckDNS requires wildcard certificates because DuckDNS provides a dynamic hostname like reg.duckdns.org such that all services will be filed under that domain, ie sonarr.reg.duckdns.org, radarr.reg.duckdns.org, etc. Typically, remembering the name is not that difficult if reg, which is the actual DuckDNS sub-domain provided, is memorable and the typing can be reduced by running an organizer container like Heimdal or Organizr. Regardless, traefik must be instructed to generate wildcard certificates for the DuckDNS domain (--entrypoints.https.http.tls.domains[0].main='DUCKDNS_DOMAIN' and --entrypoints.https.http.tls.domains[0].sans='*.DUCKDNS_DOMAIN'). Note that in this context, DUCKDNS_DOMAIN would be reg.duckdns.org.
  • The entire certificatesresolvers.LABEL.* configuration sub-tree is independent, marked by a label LABEL (duckdns in this case), and then referenced from the certificate resolver stanza --entrypoints.https.http.tls.certresolver='duckdns'.

Note that traefik is bother to understand because it is not always immediate what a reserved word is and what a bareword or label is. Here is an example, taken from the configuration that redirects HTTP to HTTPs:

            --entrypoints.http=true
            --entrypoints.http.address='0.0.0.0:80'

            --entrypoints.https=true
            --entrypoints.https.address='0.0.0.0:443'

            --entrypoints.http.http.redirections.entrypoint.to='https'
            --entrypoints.http.http.redirections.entrypoint.scheme='https'

here:

  • the very first http is a label chosen by the user; you will see this appear in the configurations sprawled on the Internet as web, for example,
  • https is also a label and is sometimes found as websecure on the Internet but it can be anything

The configuration above is identical to the following configuration when renaming the labels:

            --entrypoints.web=true
            --entrypoints.web.address='0.0.0.0:80'

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

            --entrypoints.web.http.redirections.entrypoint.to='websecure'
            --entrypoints.web.http.redirections.entrypoint.scheme='https'

and note that the last https is a configuration paramter to scheme, which is the HTTP scheme, such that it is not a label but a reserved word that can typically be h2c, http, https, etc.

Compose

# named network 'entertainment' is marked as "external" (used by service 'traefik'), so either remove "external" from network definition or it needs to be created using: docker network create -d bridge entertainment
name: <your project name>
services:
    traefik:
        container_name: traefik
        hostname: traefik
        networks:
            - entertainment
        ports:
            - 80:80
            - 443:443
            - 8080:8080
        environment:
            - DUCKDNS_TOKEN=d5c7269c-cb17-4615-b10a-0bfee32dfef7
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - /mnt/docker/data/traefik/letsencrypt:/letsencrypt
        labels:
            - traefik.enable=true
            - traefik.docker.network=entertainment
        image: traefik:3.4
        command: 
            --log=true 
            --log.level=DEBUG 
            --global.sendAnonymousUsage=false
            --global.checkNewVersion=false 
            --providers.docker=true
            --providers.docker.exposedbydefault=true
            --providers.docker.defaultrule='Host(`{{ normalize .Name}}.DUCKDNS_DOMAIN`)'
            --entrypoints.http=true
            --entrypoints.http.address='0.0.0.0:80'
            --entrypoints.http.http.redirections.entrypoint.to='https'
            --entrypoints.http.http.redirections.entrypoint.scheme='https'
            --entrypoints.https=true
            --entrypoints.https.address='0.0.0.0:443'
            --entrypoints.https.http.tls.domains[0].main='DUCKDNS_DOMAIN'
            --entrypoints.https.http.tls.domains[0].sans='*.DUCKDNS_DOMAIN'
            --entrypoints.https.http.tls.certresolver='duckdns'
            --entrypoints.https.http.tls=true
            --entrypoints.https.http.middlewares='authelia@docker'
            --certificatesresolvers.duckdns.acme.storage='/letsencrypt/acme.json'
            --certificatesresolvers.duckdns.acme.dnschallenge=true
            --certificatesresolvers.duckdns.acme.dnschallenge.provider='duckdns'
            --certificatesresolvers.duckdns.acme.email='DUCKDNS_EMAIL'
            --certificatesresolvers.duckdns.acme.dnschallenge.resolvers='8.8.8.8,8.8.4.4'
networks:
    entertainment:
        external: true
        name: entertainment

Service

[Unit]
Description=Traefik
After=docker.service
StartLimitIntervalSec=0
 
[Service]
Slice=servarr.slice
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStartPre=/usr/bin/docker pull traefik:3.4
ExecStart=/usr/bin/docker run --name=traefik \
  --rm \
  --hostname traefik \
  --net=entertainment \
  --interactive \
  -p 80:80 \
  -p 443:443 \
  -p 8080:8080 \
  -e DUCKDNS_TOKEN=d5c7269c-cb17-4615-b10a-0bfee32dfef7 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /mnt/docker/data/traefik/letsencrypt:/letsencrypt \
  -l traefik.enable=true \
  -l traefik.docker.network=entertainment \
  traefik:3.4 \
  --log=true \
  --log.level=DEBUG \
  --global.sendAnonymousUsage=false \
  --global.checkNewVersion=false \
  --providers.docker=true \
  --providers.docker.exposedbydefault=true \
  --providers.docker.defaultrule='Host(`{{ normalize .Name }}.DUCKDNS_DOMAIN`)' \
  --entrypoints.http=true \
  --entrypoints.http.address='0.0.0.0:80' \
  --entrypoints.http.http.redirections.entrypoint.to='https' \
  --entrypoints.http.http.redirections.entrypoint.scheme='https' \
  --entrypoints.https=true \
  --entrypoints.https.address='0.0.0.0:443' \
  --entrypoints.https.http.tls.domains[0].main='DUCKDNS_DOMAIN' \
  --entrypoints.https.http.tls.domains[0].sans='*.DUCKDNS_DOMAIN' \
  --entrypoints.https.http.tls.certresolver='duckdns' \
  --entrypoints.https.http.tls=true \
  --entrypoints.https.http.middlewares='authelia@docker' \
  --certificatesresolvers.duckdns.acme.storage='/letsencrypt/acme.json' \
  --certificatesresolvers.duckdns.acme.dnschallenge=true \
  --certificatesresolvers.duckdns.acme.dnschallenge.provider='duckdns' \
  --certificatesresolvers.duckdns.acme.email='DUCKDNS_EMAIL' \
  --certificatesresolvers.duckdns.acme.dnschallenge.resolvers='8.8.8.8,8.8.4.4'
ExecStop=/usr/bin/docker stop traefik
ExecStop=/usr/bin/docker rm -f traefik
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker
Environment=HOSTNAME=spark
 
[Install]
WantedBy=multi-user.target

Authelia

authelia is a middleware/groupware authentication suite to which traefik will redirect connections without a session cookie and then authelia will prompt for authentication. authelia is pretty complex with very many options and possibilities but the main focus here was placed on minimalism, the idea of just a few users that will be utilizing the stack and a balance between simplicity and convenience (ie: authenticate using web forms, not HTTP basic authentication popups that are modal for most browsers and hence annoying).

With that said, the following is the authelia configuration that will be referenced from the Docker compose and/or service file.

###########################################################################
#                   Minimal Authelia Configuration                        #
###########################################################################
# by: Wizardry and Steamworks, 2025, original by www.simplehomelab.com    #
###########################################################################
 
server:
  address: tcp://0.0.0.0:9091/
  endpoints:
    enable_pprof: false
    enable_expvars: false
  disable_healthcheck: false
  tls:
    key: ""
    certificate: ""
 
# https://www.authelia.com/configuration/miscellaneous/logging/
log:
  level: info
  format: text
  file_path: /config/authelia.log
  keep_stdout: true
 
# https://www.authelia.com/configuration/second-factor/time-based-one-time-password/
totp:
  issuer: DUCKDNS_DOMAIN
  period: 30
  skew: 1
 
# https://www.authelia.com/reference/guides/passwords/
authentication_backend:
  password_reset:
    disable: true
  refresh_interval: 5m
  file:
    path: /config/users.yml
    password:
      algorithm: argon2id
      iterations: 1
      salt_length: 16
      parallelism: 8
      memory: 256
 
# https://www.authelia.com/overview/authorization/access-control/
access_control:
  default_policy: deny
  rules:
    - domain:
        - "AUTH_DUCKDNS_DOMAIN"
        # notification services implement their own authentication
        - "gotify.DUCKDNS_DOMAIN"
      policy: bypass
    - domain:
        - "*.DUCKDNS_DOMAIN"
        - "DUCKDNS_DOMAIN"
      policy: one_factor
 
# https://www.authelia.com/configuration/session/introduction/
session:
  name: authelia_session
  same_site: lax
  expiration: 7h
  inactivity: 5m
  remember_me: 1M
  secret: '0523B3CB1E24FB555D59D5034128A9ECB0F952C7B2B43EF8A0871239F93F63CF'
  cookies:
    - domain: 'DUCKDNS_DOMAIN'
      authelia_url: 'https://AUTH_DUCKDNS_DOMAIN'
      default_redirection_url: 'https://REDIRECT_DUCKDNS_DOMAIN'
 
# https://www.authelia.com/configuration/security/regulation/
regulation:
  max_retries: 3
  find_time: 10m
  ban_time: 12h
 
# https://www.authelia.com/configuration/storage/introduction/
storage:
  encryption_key: 'B69FDB780D2A64F45B6846636FDF50E58E2BEDB168312799863AF5B9660D804D'
  local:
    path: /config/db.sqlite3
 
# https://www.authelia.com/configuration/notifications/introduction/
notifier:
  disable_startup_check: false
  filesystem:
    filename: /config/notifications.txt
 
identity_validation:
  reset_password:
    jwt_secret: 'FED54B095BA4EAEF10C1BCDDEB9A7BF6BBA05B3AF34D007D11AA61B89F16616B'

Similarly, the configuration references a users file definition that will contain all the users that will be allowed to access the system.

users:
  john:
    disabled: false
    displayname: 'John Dohe'
    password: '$argon2id$v=19$m=4096,t=3,p=1$QTF3ZmVEQzNWT3ptNk83bWpSNGdvUT09$eqdorkHj8PN+ThUp1X8F1mmQ3WbGTlj4Oh1RSJ9h4D4'
    email: 'DUCKDNS_EMAIL'
    groups:
      - 'admins'
      - 'dev'

The Argon2id password can be generated on the Linux command-line with just openssl and the argon2 Debian package installed.

Here are some particularities of the authelia compose and service file that should be mentioned: * The authelia label - traefik.http.routers.authelia.entryPoints='https' corresponds to the traefik command parameter --entrypoints.http.http.redirections.entrypoint.to='https' where https is just a label denoting the "secure" HTTP counter-part.

  • When a request is received by traefik, it is forwarded to http://authelia:9091/api/authz/forward-auth and note that interestingly authelia:9091 is not parametrized. What would be even more daunting to a new user would be that authelia does not even expose a port! What is happening here is that traefik would have already mapped authelia:9091 to the authelia container but on the entertainment network that is internal to both authelia and traefik such that the redirect from, say, sonarr.reg.duckdns.org will be sent to http://authelia:9091/api/authz/forward-auth without a hitch.

The authelia configuration has some particularities that are worth mentioning as well:

  • It should not be necessary to reset passwords and stuff like that for a "homelab setup" such that password resetting is disabled. In case a password reset is needed the user can just log-in manually and update the users.yml file.
  • authelia stores session cookies within an SQLite database (storagelocalpath) which should be sufficient even for a hundred users logging in-and-out. It is possible to cache credentials using Redis, in order to speed up. . . authentication, but seriously nobody is in that much in a rush for a simple password string comparison to make a difference!
  • One important configuration is to be found by following the hierarchy at access_controlrules. As can be observed the authentication domain itself, following the examples, auth.reg.duckdns.org is set to bypass authentication. If this were not the case, then what will happen will be that a service like sonarr.reg.duckdns.org will redirect to auth.reg.duckdns.org, which, in turn, will redirect to auth.reg.duckdns.org, which, in turn, will redirect to auth.reg.duckdns.org, etc., thereby forming a loop and at some point ending in an HTTP 431 error. What is happening here is that when traefik is configured to apply authentication to ALL services via the traefik configuration --entrypoints.https.http.middlewares='authelia@docker', then ALL services will require authentication, including the authenticating domain itself auth.reg.duckdns.org which results in the loop. In order to work around this, the authentication domain auth.reg.duckdns.org is placed as bypass, obviously, the authentication domain itself does not require authentication or we'd need to add another layer.
  • Just as a mention, another closely-related configuration to the authentication loop is specifying the domain gotify.DUCKDNS_DOMAIN as an authentication-bypass domain and this is because Gotify has its own authentication schemes that are blended with its own API such that doubling up on the authentication is superfluous. Ideally, authelia would replace all authentication for all Servarrs, for example, Sonarr should be set with its authentication to None and let authelia handle it or else the user will have to authenticate twice. However, some services like Gotify should be excluded from authentication, especially when their API is blended with the authentication scheme itself.

Compose

# named network 'entertainment' is marked as "external" (used by service 'authelia'), so either remove "external" from network definition or it needs to be created using: docker network create -d bridge entertainment
name: <your project name>
services:
    authelia:
        container_name: authelia
        hostname: authelia
        networks:
            - entertainment
        volumes:
            - /mnt/docker/data/authelia:/config
        labels:
            - traefik.enable=true
            - traefik.docker.network=entertainment
            - traefik.http.routers.authelia.rule="Host(`AUTH_DUCKDNS_DOMAIN`)"
            - traefik.http.routers.authelia.entryPoints='https'
            - traefik.http.routers.authelia.tls=true
            - traefik.http.middlewares.authelia.forwardAuth.address="http://authelia:9091/api/authz/forward-auth?rd=https://AUTH_DUCKDNS_DOMAIN/"
            - traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true
            - traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders='Remote-User,Remote-Groups,Remote-Email,Remote-Name'
        image: authelia/authelia:latest
 
networks:
    entertainment:
        external: true
        name: entertainment
 

Services

[Unit]
Description=Authelia
After=docker.service
StartLimitIntervalSec=0
 
[Service]
Slice=servarr.slice
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStartPre=/usr/bin/docker pull authelia/authelia:latest
ExecStart=/usr/bin/docker run --name=authelia \
  --rm \
  --hostname authelia \
  --net=entertainment \
  --interactive \
  -v /mnt/docker/data/authelia:/config \
  -l traefik.enable=true \
  -l traefik.docker.network=entertainment \
  -l traefik.http.routers.authelia.rule="Host(`AUTH_DUCKDNS_DOMAIN`)" \
  -l traefik.http.routers.authelia.entryPoints='https' \
  -l traefik.http.routers.authelia.tls=true \
  -l traefik.http.middlewares.authelia.forwardAuth.address="http://authelia:9091/api/authz/forward-auth?rd=https://AUTH_DUCKDNS_DOMAIN/" \
  -l traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true \
  -l traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders='Remote-User,Remote-Groups,Remote-Email,Remote-Name' \
  authelia/authelia:latest
ExecStop=/usr/bin/docker stop traefik
ExecStop=/usr/bin/docker rm -f traefik
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker
Environment=HOSTNAME=spark
 
[Install]
WantedBy=multi-user.target

Notes

  • Note that Traefik will not perform any dynamic DNS updates for DuckDNS but there are easy solutions, either by running ddclient or by running the lscr.io/linuxserver/duckdns:latest container from linuxserver.io. These services are mostly set-once and then can run unattended.

docker/traefik/templates/all-in-one.txt · Last modified: by office

Wizardry and Steamworks

© 2025 Wizardry and Steamworks

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.