Table of Contents

About

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

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:

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:

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 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.

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

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