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.
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.
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 DuckDNSAUTH_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.
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:
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).--entrypoints.http.http.redirections.entrypoint.to='https' --entrypoints.http.http.redirections.entrypoint.scheme='https'
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.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
.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:
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 anythingThe 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.
# 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
[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 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.
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:
users.yml
file.storage
→local
→path
) 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!access_control
→rules
. 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.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.# 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
[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
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.For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.