It has been over six years since I published my first Traefik guide, and then updated versions in 2020, and 2022.
Earlier this year, I published the updated 2024 version. But Traefik v3 was released on April 30, 2024 and I decided to do a quick update. This will be essentially the same as the 2024 Traefik v2 guide with the required changes for Traefik v3.
These have helped hundreds of thousands of people. To go along with this guide, I also published my setup on GitHub.
However, over the last few years, my setup has evolved and the differences between the updated guide and GitHub repo kept growing, even though I did my best to keep the Traefik v3 Docker Compose guide up-to-date.
So, this year, I started a series of Docker serve tutorials and I am taking the opportunity to update my Traefik guide.
Ultimate Docker Server Series:
This post is part of the Docker Server Tutorial Series, which includes the following individual chapters/parts:- Ultimate Docker Server: Getting Started with OS Preparation [VIDEO] [2024]
- Docker Media Server Ubuntu/Debian with 60+ Awesome Apps [VIDEO] [2024]
- ZeroTier VPN Ubuntu, Docker, Synology, Windows: Secure on-the-go access [VIDEO] [2024]
- Nginx Proxy Manager Docker Compose Guide: Simplest Reverse Proxy [coming soon]
- Traefik Reverse Proxy
- Authelia Docker Compose Guide: Secure 2-Factor Authentication [VIDEO] [2024]
- Google OAuth Docker Compose Guide: Multi-Factor Authentication [VIDEO] [2024]
- Docker Security Practices for Homelab: Secrets, Firewall, and more
- Cloudflare Settings for Docker Traefik Stacks
- Implementing a Backup System for Docker Traefik Stack [coming soon]
- Automate Homelab Setup Deployarr: 60+ Apps in Minutes
With the release of Auto-Traefik, I made many changes. In the updated basic Docker media server guide, which is a prequel to this Traefik guide, I left you all with a media server stack that is accessible internally. [Read: 60+ Best Docker Containers for Home Server Beginners]
There were many options presented to make the apps available from the internet and Traefik was one of them. Traefik is a powerful reverse proxy and it powers 3 of my 5 Docker hosts, including the web server stack that runs this website.
So, without further ado, let us begin our Traefik v3 Docker-Compose stack.
Table of Contents
- Traefik v3 Docker Server Guide
- Traefik Setup: Pre-read
- Prep Work for Traefik 3 Setup
- Traefik 3 Docker Compose
- Membership Required
- Membership Required
- Membership Required
- Additional Middlewares and Chains
- Putting Apps Behind Traefik Proxy
- Membership Required
- Additional Readings
- Troubleshooting
- Docker Compose Traefik v3 Stack - Final Thoughts
Traefik v3 Docker Server Guide
Here are the previous versions of this guide:
- Traefik Tutorial: Traefik 1 Reverse Proxy with LetsEncrypt for Docker Media Server [2018] (Traefik v2)
- Ultimate Docker Home Server with Traefik 2, LE, and OAuth / Authelia [2020] (Traefik v2)
- Ultimate Traefik Docker Compose Guide with LetsEncrypt [2022] (Traefik v2)
- Ultimate Traefik Docker Compose Guide [2024]: LE, SSL, Reverse Proxy (Traefik v2)
This guide is an update based on the evolution of my own setup and to align with the best practices implemented in my Auto-Traefik script.
In addition, I am also taking the "secure-by-design" approach and including Socket Proxy and Docker Secrets.
Traefik 3 Video Guide
If you prefer a video version, check out my Youtube video on installing Traefik reverse proxy:
My Setup and Environment
I run five Docker hosts. I sync the Docker files using Syncthing and push my setup to Github. If this is new information to you, then I strongly recommend you read the following two articles before proceeding to get the best out of this guide:
- Ultimate Docker Server: Getting Started with OS Preparation [Part 1]
- Ultimate Docker Media Server: With 60+ Docker Compose Apps [2024]
In this Traefik guide, we are going to cover most of everything there is to set up a Docker Server with Traefik 2, LetsEncrypt SSL certificates, and Authentication (Basic Auth) for security. However, frequently, I will refer you back to my previous guides for some reading to not make this guide too lengthy.
My Docker Hosts
My web server runs on Digital Ocean and my Proxmox Server runs my Home Server, Media/Database Server, and AdBlock/DNS Server as Ubuntu 24.04/22.04 LXC Containers. I recently upgraded to the TopTon V700 Mini PC as Proxmox Host (for just $481) and it's been killing it.
- TopTon V700 Mini PC with Intel 13th Gen "Raptor Lake" i7-13800H - $481
- 2x32GB Crucial DDR5 4800MHz SO-DIMM RAM - $163
- 2x2TB Crucial T500 PCIe Gen4 M.2 NVME Drives in ZFS RAID - $230
- 4TB Crucial MX500 SATA III SSD for non-critical data, cache, etc. - $200
Differences from Previous Traefik Docker Guides
As mentioned before, since the publication of the 2022 version of the Traefik guide, my setup changed significantly. You can review the commit logs on my GitHub for the sequence of changes. I try to leave detailed comments.
But here are some key changes:
- Usage of Cloudflare scoped API Token instead of Global API Key - more secure.
- As explained in Docker Guide, I now have a new folder structure with individual compose YML files for each app - this is how Auto-Traefik works too.
- Added a Docker Socket Proxy from the start for added security.
- Use Docker Secrets from the start for added security.
- Discontinue usage of Docker Extensions as the readability of compose files were compromised. You may find remnants of this in my older Docker guides.
There are many more minor changes.
Migrating from Traefik v2 to v3
If you have been following my guides over the years, then there are only 4 major changes as listed below. In addition, the official v2 to v3 migration documentation is a good reference as well.
1. Traefik AppData Folder
For Traefik v2, all the traefik app data was stored in /home/user/docker/appdata/traefik2. This has been changed to /home/user/docker/appdata/traefik3.
You may make a copy of traefik2 folder and call it traefik3 and start making edits. Or build your traefik3 folder from scratch so you can test your setup.
2. Traefik Network
Traefik network has been renamed from t2_proxy to t3_proxy. Therefore, all references of t2_proxy in compose files need to changed to t3_proxy.
Of course, feel free to customize the network name. But keep in mind that if you would like compatibility with Auto-Traefik, then use the default ones I have used/suggested.
3. New syntax for Host rule (for e.g. Homepage)
Traefik v2:
- "traefik.http.routers.homepage-rtr.rule=Host(`$DOMAINNAME_1`,`www.$DOMAINNAME_1`)"
Treafik v3:
- "traefik.http.routers.homepage-rtr.rule=Host(`$DOMAINNAME_1`) || Host(`www.$DOMAINNAME_1`)"
4. Headers and HeadersRegexp
All references of Headers and HeadersRegexp are now Header and HeaderRegexp (without the s). This is not be confused appearance of the word "headers" in other places.
For example (radarr.yml from my GitHub):
Traefik v2:
- "traefik.http.routers.radarr-rtr-bypass.rule=Host(`radarr.$DOMAINNAME_1`) && Headers(`traefik-auth-bypass-key`, `$TRAEFIK_AUTH_BYPASS_KEY`)" # Bypass Auth for LunaSea on iOS
Treafik v3:
- "traefik.http.routers.radarr-rtr-bypass.rule=Host(`radarr.$DOMAINNAME_1`) && Header(`traefik-auth-bypass-key`, `$TRAEFIK_AUTH_BYPASS_KEY`)" # Bypass Auth for LunaSea on iOS
5. HostSNIRegexp
The new syntax also applies to HostSNIRegexp. I used HostSNIRegexp only when I had to passthrough a domain to another Traefik instance.
Traefik v2:
rule: "HostSNIRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`)"
Treafik v3:
rule: "HostSNIRegexp(`example.com`) || HostSNIRegexp(`{subdomain:[a-z]+}.example.com`)"
6. Swarm Mode
Swarm mode related configurations changed in Traefik 3.0. My guides are not written for Swarm mode. And so these lines are commented out in this guide. If you use Docker Swarm be sure to know the new specifications.
Objectives of this Traefik 3 Docker Server Setup
With Traefik, I want to achieve:
- Secure access to apps over the internet without having to port-forward on the router to individual apps.
- An easy way to access the apps using user-friendly domain names instead of ports.
- A higher level of security since the apps will be exposed to the internet.
- Ability to extend the functionality of my stack using Traefik plugins.
Automating the Docker Reverse Proxy Stack Setup
If you have not heard of Auto-Traefik, then you should consider checking out this post or watching the video series below:
Auto-Traefik was launched as a perk to my supporters and to find a way to financially support what I do with this site.
Everything that the Auto-Traefik Script does should be possible by following this series without paying for Auto-Traefik. But my hope is that you continue to support my work by becoming a member.
Be the 1 in 200,000. Help us sustain what we do.You will gain benefits such as Deployarr access, discord roles, exclusive content, ad-free browsing, and more.🔥 Holiday Sale! 25% Off Platinum Membership$399.99$299.99 (ends December 31).Join the Geek Army (starting from just $1.67/month)
Traefik Setup: Pre-read
Let's first start with some basic information on Traefik. Knowing these will save you some time in the long run. So don't skip.
In a nutshell, we are going to use Traefik mainly to:
- Put our apps behind a convenient and easy-to-remember URL
- Add basic HTTP authentication for the apps
- Add security headers for the web interfaces of the apps
- Reduce security risks by avoiding port forwarding to individual apps (not exposing them directly to the internet)
- Put all our services behind LetsEncrypt SSL certificates that are automatically pulled and renewed by Traefik 3
In my first Traefik guide, I discussed two ways for accessing your apps from the internet: subdirectory and subdomain.
In this guide, for the sake of convenience, we are only going to show you the subdomain method with a private domain name. If you want to go with the subdirectory method, you can combine the information from my previous guide to figure things out (in my opinion, it is worth spending a few bucks on a domain name - only $8 on Cloudflare per year).
Traefik Reverse Proxy Overview
Traefik reverse proxy provides convenience and security for your internet-facing services (e.g. Web Apps, Radarr, Sonarr, SABnzbd, WordPress, Nginx, etc.). A reverse proxy server typically sits behind a firewall (router or internet gateway) and directs clients to the appropriate apps using a common name (radarr.example.com) without the client having to know the server's IP address or port. The client interacts with the reverse proxy and the reverse proxy directs the communication to the back-end app to provide/retrieve information.
Basic information on reverse proxy has already been covered in detail in my previous Traefik tutorial. Here are the links to relevant sections for your review:
- What is Reverse Proxy?
- What is Traefik Reverse Proxy?
- Benefits of Reverse Proxy
- Alternatives to Traefik - Nginx Proxy and HAProxy
Traefik vs Nginx Proxy Manager
Traefik offers several advanced features over Nginx Proxy Manager:
- Scalability.
- Metrics.
- Traefik Pilot Integration.
- Easier way to configure proxies via Docker Compose, instead of GUI.
- Better integration with Authentication services like Google Oauth and Authelia, compared to just basic HTTP authentication in NPM.
- And Enterprise support.
For me, the integration with authentication services is a big deal. In addition, I can also do some advanced routing that allows bypassing authentication for certain apps (eg. Radarr or Sonarr remote mobile phone app).
Traefik 3 Routers, Middlewares, and Services
Traefik v3 uses three major components in the configuration: Routers, Services, and Middlewares.
Routers
Routers are like frontends; they manage the incoming requests. In the routers section, you’ll define the entrypoint, certificate resolver, and define the rules for the request.
Services
Services are like backends, they identify where to send requests. This is where you can define the port that Traefik will proxy to and any additional load balancers.
Middlewares
Middlewares are one of the exciting new features of Traefik v3. Middlewares modify the request, and indeed they are the "middleware" between the routers and services. You can easily add things like headers, authentication, path-prefix, or combine them and create reusable groups.
With these three concepts, we c identify the incoming request, define where the request is going, and choose how we want to modify the request as it is routed.
Traefik 3 Configuration
There are multiple ways to configure Traefik 3 and this can be quite overwhelming to newbies. Even with some experience, I find it difficult to wrap my head around Traefik 3 configuration methods at times.
So, let's spend time understanding some important Traefik 3 configuration details: static vs dynamic configuration.
Dynamic Configuration
Traefik is a dynamic reverse proxy, meaning it can add and remove routes automatically when containers start or stop.
Dynamic configuration can be specified in two places:
- With the Docker provider by using labels in the Docker Compose for each service.
- With our File provider - YML files inside the "rules" folder (explained later).
Static Configuration
While dynamic methods are one of the main advantages of Traefik, it’s important to understand that Traefik also uses a static configuration which is defined differently, and must be kept separate.
There are three different ways to define the static configuration for Traefik 3. Only one can be used at the same time.
- Configuration file - can be in a TOML or YAML file (e.g. traefik.toml or traefik.yml).
- Command-line (CLI) arguments - these arguments are passed during docker run (this is what we will be using).
- As environment variables - here is a list of all environmental variables.
These ways are evaluated in the order listed above.
Getting Started with Traefik v3
Here are a few tips that I recommend while setting up Traefik 3.0 to hopefully make things a bit easier:
- Keep It Simple: Sometimes it can be difficult to troubleshoot a problem when a lot of things are changed at once. Start with a basic example and access the Traefik dashboard first, then continue to add services from there.
- Go Formatting: Traefik is written in Go, and therefore we need to use Go formatting depending on the input type (string, boolean, array). This means, for example, that your hostname must be defined with backticks, such as `traefik.example.com` (apostrophes will not work!).
That completes most of the basic information that you might need. Let's move on to setting up our Traefik v3 Docker Compose stack.
Prep Work for Traefik 3 Setup
Next, let’s do some prep work to get the Traefik v3 Docker container up and running.
Requirements for Docker Traefik Setup
Several requirements must be met before proceeding with this Docker tutorial for setting up microservices behind Traefik reverse proxy. Having a one the compatible/recommended operating systems, preparing the OS, and installing Docker and Docker Compose were already listed as a requirement in previous guides.
Those are required for Traefik as well. In addition, the following requirements must be met:
- Cloudflare (optional) - Although Traefik works with other providers, I use and recommend Cloudflare and that is what I will use as example in this guide.
- Domain Name - In this guide, I am going to use simplehomelab.com. I highly recommended getting your domain from Cloudflare (I get no commission to say this). It has one of the cheapest and most straightforward pricing. Plus, WHOIS privacy is included.
- Proper DNS Records - A minimum of 2 records. A record pointing to WAN IP and CNAME record (or a wildcard (*)) pointing to the root domain.Note: If you orange-cloud the DNS records on Cloudflare (i.e. Cloudflare proxy enabled) then you will not see the LetsEncrypt certificate when it is pulled. Since your service is behind Cloudflare proxy, you will see Cloudflare's SSL certificate. Therefore, during initial testing and setup I recommend leaving the proxy off (gray-cloud). After ensuring that proper LetsEncrypt certificates are pulled, you can enable Cloudflare proxy, following which you will only see Cloudflare's SSL certificates.
- Cloudflare SSL Settings - Full SSL
- Port 80 and 443 Forwarded from your Internet gateway/router to the Docker host running Traefik.
Check the above sections to ensure you have everything before proceeding with this Traefik v3 Docker Compose guide.
Take a look:
Allow Ports 80 and 443 on Firewall
If you have been following this series from the start, then in Part 1, we enabled firewall and allowed incoming connections only from the LAN network.
But Traefik requires Ports 80 and 443 to be accessible from anywhere. So, let's add the following UFW firewall rules:
sudo ufw allow 80 sudo ufw allow 443
Creating/Adapting Docker Environment
If you haven't done so, create the Docker Environment (files and folders shown below) as described in the Docker media server guide.
We are going to use the same folder structure that was used for our Docker media server guide.
In addition, at this point, we should have a /home/user/docker/.env file that looks similar to this:
PUID=1000 PGID=1000 TZ="Europe/Zurich" USERDIR="/home/anand" DOCKERDIR="/home/anand/docker" DATADIR="/media/storage" HOSTNAME="udms"
Additional Environment Variables for Traefik v3 Docker Server
Let's expland the .env file shown above to add a few more environment variables at the bottom:
PUID=1000 PGID=1000 TZ="Europe/Zurich" USERDIR="/home/anand" DOCKERDIR="/home/anand/docker" DATADIR="/media/storage" HOSTNAME="udms" DOMAINNAME_1=simplehomelab.com LOCAL_IPS=127.0.0.1/32,10.0.0.0/8,192.168.0.0/16,172.16.0.0/12 CLOUDFLARE_IPS=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22
simplehomelab.com, will be the domain we use in this guide (of course replace it with your own domain name).
LOCAL_IPS and CLOUDFLARE_IPS are lists of IPs that Traefik can trust. They are pretty standard and do not require any customizations. The ranges defined will also cover the IPs set by ZeroTier or similar services for internal networks, if you decide to implement those later on.
Create Basic HTTP Authentication Secret
Basic HTTP authentication provides a layer of security. You will have to provide a username and password before you can access the app that is behind Traefik.
In my previous guides, I created this as .htpasswd file in the shared folder. But we are going to go secure-by-design route. So, in this updated version of the guide, I am going to use Docker Secrets.
Create the Basic Auth credentials secret file using the following command:
sudo htpasswd -cBb /home/anand/docker/secrets/basic_auth_credentials HTTP_USERNAME HTTP_PASSWORD sudo chown root:root /home/anand/docker/secrets/basic_auth_credentials
Replace HTTP_USERNAME and HTTP_PASSWORD with your chosen username and password for Basic authentication. We have now created the secret file that will be used later on in this guide.
Alternate Method
Use this HTPASSWD Generator, to create a username and password and add them to the /home/user/docker/secrets/basic_auth_credentials file as shown below:
username:mystrongpassword
Replace/Configure:
- username: with your HTTP username.
- mystrongpassword: with your hashed HTTP password generated using the link above.
user:$apr1$bvj3f2o0$/01DGlduxK4AqRsTwHnvc1
Should be:
user:$$apr1$$bvj3f2o0$$/01DGlduxK4AqRsTwHnvc1
In the environment file (.env), $ signs do not need to be escaped.
Create Cloudflare DNS API Token Secret
In this guide, we will be using the DNS Challenge method to make Traefik get wildcard certificates from LetsEncrypt. To do that with Cloudflare we need one of the following (increasing order of security):
- Cloudflare Email + Global API Key
- Cloudflare Scoped DNS API Token
- Cloudflare DNS API Token + Zone API Token
If you use one of the other DNS providers instead of Cloudflare, make sure to include the required configuration parameters in the .env or preferably as secrets.
Let's create a Docker Secret for Cloudflare scoped DNS API token.
On your Cloudflare account, go to Profile->API Tokens, and then create a new "Custom" token as shown below.
You can name the token whatever you want (suggested: CF_DNS_API_TOKEN). Ensure Zone/Zone/Read and Zone/DNS/Edit privileges. Leave Zone Resources to "All Zones" and the rest empty. Create the token and copy/note it.
Next, create a new secret file /home/user/docker/secrets/cf_dns_api_token:
sudo nano /home/anand/docker/secrets/cf_dns_api_token
Note that you have to use sudo as the file is inside a secrets folder that is owned by the user root. Paste the copied token contents from previous step, as shown below:
Press Ctrl X, Y, and Enter to save and exit. We have now created the secret file that will be used later on in this guide.
In this guide, for simplicity, I am going to create CF_DNS_API_TOKEN for all zones. This is how I do it and Auto-Traefik works at this point.
Prepare Traefik 3 Folders and Files
Finally, we need to create new folders for Traefik and ACME in the docker appdata folder (/home/user/docker/appdata) described previously:
mkdir traefik3 mkdir traefik3/acme mkdir traefik3/rules mkdir traefik3/rules/udms
traefik3 is the folder we will use to store all Traefik 3.0 related configurations. udms is the hostname of our ultimate docker media server (udms), we set in the previous Docker guide.
Note that Docker cannot create missing files (only directories). So, we will have to create some empty files manually.
Next, let's create an empty file for Traefik to store our LetsEnrypt certificate. Create acme.json empty file inside appdata/traefik3/acme folder using the following command
touch acme.json
Set proper permission for acme.json file using the following command (from inside appdata/traefik3/acme):
chmod 600 acme.json
The acme.json file will store all the SSL certificates that are generated. In the later steps of this guide, you will be asked to open and check this file.
Similarly, we are going to create log files for Traefik to write logs to. I store all my logs in a centralized location (/home/user/docker/logs). Let's create a few additional folders for this specific host:
mkdir /home/anand/docker/logs/udms mkdir /home/anand/docker/logs/udms/traefik
Next, from within the logs/udms/traefik folder, let us create empty log files:
touch traefik.log touch access.log
I am defining a separate Traefik log and Access log because I am setting the stage for implementing Crowdsec + Traefik Bouncer later on. CrowsdSec is a free and awesome intrusion prevention system that is a great alternative to Fail2ban.
Traefik 3 Docker Compose
sudo docker compose -f /home/anand/docker/docker-compose-udms.yml down
All basic information has been presented and the prep work has been done. Let's start from where we left off in my docker media server guide. We should now have a system with Docker and Docker Compose running, along with a few apps.
But if you skipped the first part, do not worry. I will provide enough details to follow along.
Let's start building the docker-compose-udms.yml file.
At the end of the Docker server guide, our docker-compose-udms.yml file started like this:
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24
If you skipped the previous guide, then create docker-compose-udms.yml with the above contents.
Define Network for Traefik v3 Docker Compose
In addition to the networks defined previously, let's add a new network for Traefik. The networks block shown above, will now become:
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24 t3_proxy: name: t3_proxy driver: bridge ipam: config: - subnet: 192.168.90.0/24
Of course, feel free to customize the network name. But keep in mind that if you would like compatibility with Auto-Traefik, then use the default ones I have used/suggested.
All services behind Traefik Proxy network will use IP addresses between 192.168.90.1 and 192.168.90.254.
Docker Secrets for Traefik
So far, we have already created two secret files: basic_auth_credentials and cf_dns_api_token. Now it is time for Step 2 in the process of adding Docker secrets.
Let's define the two secrets globally in the master docker-compose-udms.yml file. Right below the networks block, add the secrets block as shown below:
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24 t3_proxy: name: t3_proxy driver: bridge ipam: config: - subnet: 192.168.90.0/24 ########################### SECRETS secrets: basic_auth_credentials: file: $DOCKERDIR/secrets/basic_auth_credentials cf_dns_api_token: file: $DOCKERDIR/secrets/cf_dns_api_token
The final 2 steps involve using the secrets inside Traefik v3 Docker Compose, which we will do later.
Start Building Docker Compose for Traefik Stack
As mentioned above, we will create individual YML files for each service. We will accomplish this using the include block in Docker Compose. If you already followed my 2024 Docker media server guide, you should already have a few services listed in your master docker-compose-udms.yml file.
Right below the network block, let us start adding our services/containers. First, begin by adding the following lines, starting from include::
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24 t3_proxy: name: t3_proxy driver: bridge ipam: config: - subnet: 192.168.90.0/24 ########################### SECRETS secrets: basic_auth_credentials: file: $DOCKERDIR/secrets/basic_auth_credentials cf_dns_api_token: file: $DOCKERDIR/secrets/cf_dns_api_token include: ########################### SERVICES # PREFIX udms = Ultimate Docker Media Server # HOSTNAME=udms - defined in .env
Notice all the comments. As you will see below, we are going to use udms as prefix for the individual compose files.
Core Services
I call these core services because they are kind of the most basic/core apps in my stack. Let us begin by adding the comment line (# CORE) below (don't ignore the 2 blank spaces in the front) under the include block.
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24 t3_proxy: name: t3_proxy driver: bridge ipam: config: - subnet: 192.168.90.0/24 ########################### SECRETS secrets: basic_auth_credentials: file: $DOCKERDIR/secrets/basic_auth_credentials cf_dns_api_token: file: $DOCKERDIR/secrets/cf_dns_api_token include: ########################### SERVICES # PREFIX udms = Ultimate Docker Media Server # HOSTNAME=udms - defined in .env # CORE - compose/$HOSTNAME/socket-proxy.yml
There are many services that I call "Core", in my GitHub repo. In this Docker server tutorial, I am only going to show Socket Proxy, Traefik, and Portainer.
Create Traefik v3 Docker Compose
Our master docker-compose-udms.yml is ready. Let's now create the individual compose YMLs for services, starting with Traefik.
Head over to the compose folder in my Github Repository, and then into any of the host folders. Find the compose file for Traefik and copy the contents.
Create a file called traefik.yml inside /home/anand/docker/compose/udms. Copy-paste the contents into traefik.yml compose file (pay attention to blank spaces at the beginning of each line).
services: # Traefik 3 - Reverse Proxy traefik: container_name: traefik image: traefik:3.0 security_opt: - no-new-privileges:true restart: unless-stopped # profiles: ["core", "all"] networks: t3_proxy: ipv4_address: 192.168.90.254 # You can specify a static IP socket_proxy: command: # CLI arguments - --global.checkNewVersion=true - --global.sendAnonymousUsage=true - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --entrypoints.traefik.address=:8080 - --entrypoints.websecure.http.tls=true - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.web.http.redirections.entrypoint.permanent=true - --api=true - --api.dashboard=true # - --api.insecure=true #- --serversTransport.insecureSkipVerify=true # Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/ - --entrypoints.websecure.forwardedHeaders.trustedIPs=$CLOUDFLARE_IPS,$LOCAL_IPS - --log=true - --log.filePath=/logs/traefik.log - --log.level=DEBUG # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC - --accessLog=true - --accessLog.filePath=/logs/access.log - --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines - --accessLog.filters.statusCodes=204-299,400-499,500-599 - --providers.docker=true # - --providers.docker.endpoint=unix:///var/run/docker.sock # Disable for Socket Proxy. Enable otherwise. - --providers.docker.endpoint=tcp://socket-proxy:2375 # Enable for Socket Proxy. Disable otherwise. - --providers.docker.exposedByDefault=false - --providers.docker.network=t3_proxy # - --providers.docker.swarmMode=false # Traefik v2 Swarm # - --providers.swarm.endpoint=tcp://127.0.0.1:2377 # Traefik v3 Swarm - --entrypoints.websecure.http.tls.options=tls-opts@file # Add dns-cloudflare as default certresolver for all services. Also enables TLS and no need to specify on individual services - --entrypoints.websecure.http.tls.certresolver=dns-cloudflare - --entrypoints.websecure.http.tls.domains[0].main=$DOMAINNAME_1 - --entrypoints.websecure.http.tls.domains[0].sans=*.$DOMAINNAME_1 # - --entrypoints.websecure.http.tls.domains[1].main=$DOMAINNAME_2 # Pulls main cert for second domain # - --entrypoints.websecure.http.tls.domains[1].sans=*.$DOMAINNAME_2 # Pulls wildcard cert for second domain - --providers.file.directory=/rules # Load dynamic configuration from one or more .toml or .yml files in a directory - --providers.file.watch=true # Only works on top level files in the rules folder - --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt Staging Server - uncomment when testing - --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53 - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.delayBeforeCheck=90 # To delay DNS check and reduce LE hitrate ports: - target: 80 published: 80 protocol: tcp mode: host - target: 443 published: 443 protocol: tcp mode: host # - target: 8080 # need to enable --api.insecure=true # published: 8085 # protocol: tcp # mode: host volumes: - $DOCKERDIR/appdata/traefik3/rules/$HOSTNAME:/rules # Dynamic File Provider directory # - /var/run/docker.sock:/var/run/docker.sock:ro # Enable if not using Socket Proxy - $DOCKERDIR/appdata/traefik3/acme/acme.json:/acme.json # Certs File - $DOCKERDIR/logs/$HOSTNAME/traefik:/logs # Traefik logs environment: - TZ=$TZ - CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token - HTPASSWD_FILE=/run/secrets/basic_auth_credentials # HTTP Basic Auth Credentials - DOMAINNAME_1 # Passing the domain name to traefik container to be able to use the variable in rules. secrets: - cf_dns_api_token - basic_auth_credentials labels: - "traefik.enable=true" # HTTP Routers - "traefik.http.routers.traefik-rtr.entrypoints=websecure" - "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME_1`)" # Services - API - "traefik.http.routers.traefik-rtr.service=api@internal" # Middlewares - "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file" # For Basic HTTP Authentication
The middlewares line (last line) may look different compared to my Github. This is because what is shown above is the most basic configuration to start with. As you go through the rest of the guide, we will continue to improve it.
Be the 1 in 200,000. Help us sustain what we do.You will gain benefits such as Deployarr access, discord roles, exclusive content, ad-free browsing, and more.🔥 Holiday Sale! 25% Off Platinum Membership$399.99$299.99 (ends December 31).Join the Geek Army (starting from just $1.67/month)
Customizing Traefik v3 Docker Compose
That was a long Docker Compose for Traefik. Let's breakdown.
Traefik Image and Restart Policy
# Traefik 3 - Reverse Proxy traefik: container_name: traefik image: traefik:3.0 security_opt: - no-new-privileges:true restart: unless-stopped # profiles: ["core", "all"]
Nothing fancy here. We are also specifying which version of Traefik to use (2.10 at the time of publishing this guide).
With no-new-privileges:true we are preventing the container from gaining additional new privileges without explicit authorization.
I have commented out profiles.
If you copy-paste from my GitHub Repository, remember to do the same for all other apps. I use the Docker profiles for some automations. You do not need it when you start out.
Traefik Dashboard Network
You can either let Docker assign a dynamic IP for the traefik service or manually assign a static IP.
networks: t3_proxy: ipv4_address: 192.168.90.254 # You can specify a static IP socket_proxy:
We are attaching the Traefik container to two networks: t3_proxy and socket_proxy. All other services that are also attached to t3_proxy can be put behind traefik easily using Docker Labels.
The example above is for a static IP on the Docker network of 192.168.90.254. This IP is only accessible by the host and on the Docker network. Without this configuration, a dynamic IP will be assigned.
Finally, we are making Traefik a part of socket_proxy enables accessing the Docker Socket (required by Traefik), via Socket Proxy that was discussed previously.
Traefik Static CLI Configuration
Reminder that static configuration can be provided using two ways: traefik.yml (or .toml) and CLI. With a dedicated traefik.yml file, all configurations are done and out of sight for the most part as they won't appear in the Traefik v3 Docker Compose file.
CLI commands, as you can see above, make the Docker Compose file long have some minor limitations in terms of features (here is a good comparison). Pick whichever one jives with your preference.
I started with traefik.toml in the first version of the Traefik guide in 2018. Then to CLI as it was easier to manage everything in one location.
I have also dabbled with traefik.yml but at this point, I am sticking with CLI commands.
There is a comparable traefik.yml file in my Github repo for those that are interested.
Some Basic CLI Command Notes
We are:
- Setting Traefik to check for new version during container start. You will see a notification in the logs (traefik.log).
- Allowing Traefik to send anonymous usage information to Traefik labs. This is a personal preference.
- Creating two entrypoints http (80) and https (443). Some online documentation may refer to these as web and websecure.Note that in my previous guides, entrypoints on port 80 and 443 were named http and https. I have renamed these to web and websecure to align with some of the online documentation you may find.
- Enabling api.dashboard but setting api.insecure to false (default) - this will disable Traefik dashboard on port 8080. This is fine as we will make it available via FQDN (e.g. https://traefik.simplehomelab.com).
- Leaving serversTransport.insecureSkipVerify as false (default). Certain apps that require HTTPS for their web UI can be finicky behind Traefik (e.g.. NextCloud, Unifi Controller, Proxmox). If you set insecureSkipVerify to true this disables SSL certificate verification. Doing so makes those finicky apps work but we want to avoid this if possible. We can use TCP routers (described later) that will allow us to access those services that require HTTPS and have a self-signed certificate.
Using the following lines, we are also setting a global HTTP to HTTPS redirect for Traefik, which will apply to all services:
- --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.web.http.redirections.entrypoint.permanent=true
--entrypoints.websecure.forwardedHeaders.trustedIPs
When using Cloudflare and all your traffic is behind their proxy (orange cloud in the DNS records), the request's origin IP is replaced with Cloudflare's IP (CLOUDFLARE_IPS). The result is that all of your services will know that a Cloudflare IP has connected, but you can't see the actual origin IP. This line tells Traefik to trust forwarded headers information (X-Forwarded-*) for the publicly available Cloudflare IPs, which means that Traefik will accept the X-Forwarded-For header set by Cloudflare.
This option is not needed if you are not using Cloudflare's proxy features (DNS entries are grey-clouded). The same applies for Local IP addresses (LOCAL_IPS).
Logging
We are setting Traefik to log to traefik.log file. To begin, a DEBUG log level is better. Once you ensure everything is working fine and LetsEncrypt Certificates are being pulled correctly, you can change this to INFO or WARN.
We are also turning on access logging to access.log asking Traefik to wait until at least 100 lines are buffered in the RAM before writing to the file.
We are also logging access logs with status codes: 204-299,400-499,500-599. This allows us to filter out successful access and redirects, which can be a lot. In other words, we want to log only abnormal access statuses.
Docker Provider
We are enabling Docker provider, which would allow us to use labels to interact with containers.
- --providers.docker=true # - --providers.docker.endpoint=unix:///var/run/docker.sock # Disable for Socket Proxy. Enable otherwise. - --providers.docker.endpoint=tcp://socket-proxy:2375 # Enable for Socket Proxy. Disable otherwise. - --providers.docker.exposedByDefault=false - --providers.docker.network=t3_proxy # - --providers.docker.swarmMode=false # Traefik v2 Swarm # - --providers.swarm.endpoint=tcp://127.0.0.1:2377 # Traefik v3 Swarm
Since we are using Socket Proxy, we will comment out providers.docker.endpoint=unix:///var/run/docker.sock and use providers.docker.endpoint=tcp://socket-proxy:2375.
We are setting the docker containers to not be exposed to Traefik by default. This means Traefik has to be enabled for each service explicitly using traefik.enable=true label.
Finally, we are setting the network as t3_proxy and turning off Docker Swarm mode.
--entrypoints.websecure.http.tls.options=tls-opts@file
For TLS connections, Traefik will use a few default options. But we can strengthen this a bit by defining some custom TLS options.
Create a file called tls-opts.yml in appdata/traefik3/rules/udms (udms is the hostname of our Docker server) folder and add the following contents to it before you start Traefik.
tls: options: tls-opts: minVersion: VersionTLS12 cipherSuites: - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 - TLS_AES_128_GCM_SHA256 - TLS_AES_256_GCM_SHA384 - TLS_CHACHA20_POLY1305_SHA256 - TLS_FALLBACK_SCSV # Client is doing version fallback. See RFC 7507 curvePreferences: - CurveP521 - CurveP384 sniStrict: true
You can find an example of tls-opts.yml file in my Traefik Docker-Compose Github repo. Nothing to customize in this file.
--providers.file.directory=/rules
In this folder, we will store some configurations that Traefik 3 can pick up dynamically in real-time (if --providers.file.watch is set to true), without needing a restart. Examples include middlewares, configurations for proxying external or non-docker apps, etc. Some example rules and scenarios are discussed later in this Docker Traefik 3 tutorial.
Cert Resolver and Domains
With the following line we are setting dns-cloudflare as the DNS resolver. I’ve chosen arbitrary names to help describe their function, for example, I’ve chosen dns-cloudflare because I’m using the DNS challenge and Cloudflare is my provider.
The name can be changed to anything, but it must be the same as the certresolver (entrypoints.websecure.http.tls.certresolver=dns-cloudflare) we set previously. The settings for this resolver are set in the final few lines (discussed below).
# Add dns-cloudflare as default certresolver for all services. Also enables TLS and no need to specify on individual services - --entrypoints.websecure.http.tls.certresolver=dns-cloudflare - --entrypoints.websecure.http.tls.domains[0].main=$DOMAINNAME_1 - --entrypoints.websecure.http.tls.domains[0].sans=*.$DOMAINNAME_1 # - --entrypoints.websecure.http.tls.domains[1].main=$DOMAINNAME_2 # Pulls main cert for second domain # - --entrypoints.websecure.http.tls.domains[1].sans=*.$DOMAINNAME_2 # Pulls wildcard cert for second domain
We have the option to have Traefik pull the LetsEncrypt certificates for multiple domains. At this point, we are only enabling one domain called using the variable $DOMAINNAME_1.
--certificatesResolvers.dns-cloudflare.acme Lines
Now we are going to talk about the last 4 CLI arguments:
- --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt Staging Server - uncomment when testing - --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53 - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.delayBeforeCheck=90 # To delay DNS check and reduce LE hitrate
This is where we define our certificate resolver(s). In this guide, we're using the ACME DNS challenge with Cloudflare as our provider, so I've chosen dns-cloudflare as the name for this cert resolver.
If you are using another ACME challenge or a DNS verification provider other than Cloudflare you may want to name your cert resolver differently.
caServer: This is a very important line. When enabled, we will be using the LetsEncrypt staging server to check if everything is working fine. As mentioned previously, LetsEncrypt has a rate limit to prevent system abuse. In case you have errors in your Traefik 3 Docker Compose, you may be locked out of LetsEncrypt validation.
To prevent this, we will use the staging server for the initial setup. Once we ensure everything is working well (shown later) we will comment out this line and have Traefik 3 get the real LetsEncrypt SSL certificates from the real server.
Traefik 3 Ports
Next, we are going to specify the port information in the Traefik Docker-Compose file. Traefik needs 80 and 443, and optionally 8080.
ports: - target: 80 published: 80 protocol: tcp mode: host - target: 443 published: 443 protocol: tcp mode: host # - target: 8080 # need to enable --api.insecure=true # published: 8085 # protocol: tcp # mode: host
We are not exposing Traefik dashboard locally. This is because we will make it available securely via Reverse Proxy, once Traefik is up and running.
But if you wanted to make the dashboard available via HTTP, locally on your LAN, you will have to set api.insecure to true.
Let's assume that port 8080 is not free on our Docker host, for the Traefik dashboard. We could change the change the published port to any port that is not occupied (e.g. 8085, assuming its free).
Port Availability
You may check if a port is occupied on the host machine using the following command:
This will show all ports on which some service is listening, as shown below:
Traefik 3 Volumes
Here are the volumes that I have specified in my Traefik 3 docker-compose file:
volumes: - $DOCKERDIR/appdata/traefik3/rules/$HOSTNAME:/rules # Dynamic File Provider directory # - /var/run/docker.sock:/var/run/docker.sock:ro # Enable if not using Socket Proxy - $DOCKERDIR/appdata/traefik3/acme/acme.json:/acme.json # Certs File - $DOCKERDIR/logs/$HOSTNAME/traefik:/logs # Traefik logs
The rules volume contains the configuration for our File provider (eg. middlewares, rules for external/non-docker apps). More on these later in this Traefik docker-compose reverse proxy guide.
Reminder that $HOSTNAME here will be replaced with udms automatically (as defined in the .env file).
The rest of the volumes/files can be used as-is.
Traefik Environmental Variables
We are going to pass the following environmental variables for Traefik 3 service to use:
environment: - TZ=$TZ - CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token - HTPASSWD_FILE=/run/secrets/basic_auth_credentials # HTTP Basic Auth Credentials - DOMAINNAME_1 # Passing the domain name to traefik container to be able to use the variable in rules.
We are passing our system Timezone to Traefik container.
DOMAINNAME_1 is the variable in .env file hosting the domain name (e.g. simplehomelab.com). The reason we are passing the domain name as an environment variable (note that it is not used in Traefik v3 docker compose) is to be able to use with file providers to put external apps behind Traefik (discussed later).
We are pointing two variables CF_DNS_API_TOKEN_FILE and HTPASSWD_FILE to their respective secret files we created previously. This is step 3 in the process of creating Docker Secrets.
Traefik 3 Docker Labels
The last one is a big one: Traefik docker-compose labels.
The first is the line to enable or disable traefik for services. Quite simple.
labels: - "traefik.enable=true"
When the container starts a route will automatically be created. This is necessary because we’ve specified exposedByDefault=false as part of our static configuration (CLI commands).
Then, we add additional routers for entrypoints to the Traefik service and under what host the dashboard should be available at:
# HTTP Routers - "traefik.http.routers.traefik-rtr.entrypoints=websecure" - "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME_1`)"
By default, Traefik will listen for incoming requests on all available entrypoints. You can limit or specify an entrypoint if you’d like to do so. In the above case, Traefik will listen on only websecure entry point (HTTPS on port 443).
A rule is how we define which requests this router will apply to. The majority of our containers will use the Host(`FQDN`) rule, but you could use regex, pathprefix or other options as well.
You'll also notice I'm using different names for each router, for example traefik-rtr. Routers are grouped using these names, but the name can be changed as you like to describe the router.
Next, we will define where to find the service to be proxied. With the insecure API disabled, we tell Traefik to find the API internally in the container using the following line.
# Services - API - "traefik.http.routers.traefik-rtr.service=api@internal"
The Traefik dashboard/API is a special case and should be defined exactly as api@internal. We will do this differently for the rest of the services in this guide.
Traefik 3 Middlewares
At this point, we could be done. We could start the Traefik container and if all goes well LetsEncrypt certificates will be pulled and Traefik dashboard should be available at https://traefik.simplehomelab.com.
But with the Traefik dashboard not having a built-in authentication, it will be visible for anyone to see. They will know what you are running, the routes, the ports, etc.
As said before, we are going to build in at least basic security features from the get go. We can accomplish this using middlewares.
Middlewares - Basic HTTP Authentication
Basic HTTP Authentication is the simplest form of authentication you can put your services behind. Let's create our first middleware using our File provider.
Let's create our second file provider (yes "second" - remember tls-opts.yml?). Create file named middlewares-basic-auth.yml inside the Traefik 3 rules folder ($DOCKERDIR/appdata/traefik3/rules/udms) and add the following content to it:
http: middlewares: middlewares-basic-auth: basicAuth: # users: # - "user:$apsdfswWvC/6.$E3FtsfTntPC0wVJ7IUVtX1" usersFile: "/run/secrets/basic_auth_credentials" realm: "Traefik 3 Basic Auth"
The above code block adds basic authentication middleware. We can specify users for authentication right here if you wish (the commented-out lines).
But as discussed previously, we are going to use basic_auth_credentials secret file we created previously. So, we will specify the path to usersFile.
Now let us add this middleware to our Traefik v3 Docker Compose service. This requires defining the middlewares we want to use on our router. At the end of the Docker Compose for Traefik (right after the api@internal line we added previously), add the following:
# Middlewares - "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file"
We are basically asking Traefik to look for the middlewares-basic-auth middleware that we defined with our File provider (hence @file) in the rules folder.
Note that the underlined part of middlewares-basic-auth@file matches the name specified in the middlewares-basic-auth.yml highlighted below (another commonly overlooked part).
Add Traefik to the Docker Stack
We have now built our Traefik v3 Docker Compose file and customized it. However, it is still not part of our stack. We will have to add it to our docker-compose-udms.yml (Master Docker Compose) file. To do so, add the path to the traefik.yml file (compose/udms/socket-proxy.yml) under the include block, as shown below:
########################### NETWORKS networks: default: driver: bridge socket_proxy: name: socket_proxy driver: bridge ipam: config: - subnet: 192.168.91.0/24 t3_proxy: name: t3_proxy driver: bridge ipam: config: - subnet: 192.168.90.0/24 ########################### SECRETS secrets: basic_auth_credentials: file: $DOCKERDIR/secrets/basic_auth_credentials cf_dns_api_token: file: $DOCKERDIR/secrets/cf_dns_api_token include: ########################### SERVICES # PREFIX udms = Ultimate Docker Media Server # HOSTNAME=udms - defined in .env # CORE - compose/$HOSTNAME/socket-proxy.yml - compose/$HOSTNAME/traefik.yml
Save the Master Docker Compose file. Reminder that $HOSTNAME here will be replaced with udms automatically (as defined in the .env file).
Testing Docker Traefik 3 Setup
The majority of the Traefik configuration steps are done. From now on, it is more of fine tuning and improvement. But before that, let's start Traefik and ensure:
- LetsEncrypt Staging Certificates are pulled successfully
- Traefik Dashboard is available
Create the Traefik container using the following command. Review the Docker commands at this point if needed.
Alternatively, you may use my Bash Aliases for Docker to simplify the commands:
sudo docker compose -f /home/anand/docker/docker-compose-udms.yml up -d
If you prefer to use Docker Profiles, then "--profile profile_name" should be included with all Docker Compose commands (e.g. sudo docker compose --profile all -f /home/anand/docker/docker-compose-udms.yml up -d).
Immediately, let's start following the logs for the Traefik service to look for any obvious errors.
tail -f /home/anand/docker/logs/udms/traefik/traefik.log
This still works for other containers but not for Traefik (all you will see is Configuration loaded from flags). Even Dozzle will not work. Here is why:
With the Traefik log level set to DEBUG, here are some messages that show that everything went well:
- Waiting for DNS record propagation
- The server validated our request
- Validations succeeded, requesting certificates
- Server responded with a certificate
At this point, if you see any "bad certificate" or "unknown certificate" messages in the log, ignore them. Remember that we are using the LetsEncrypt staging server, which does not provide valid certificates yet.
There are multiple ways to check if Traefik 3 is configured correctly and succeeded in obtaining the LetsEncrypt certificate. I will show you two of them here.
Let's open Traefik 3 dashboard in a browser and see the certificate information, as shown below. Notice that it says (STAGING). This shows that an SSL (fake) certificate was obtained by Traefik 3 from the LetsEncrypt staging server.
Alternatively, you can open acme.json file located inside the appdata/traefik3/acme folder and look for signs of successful validation, as shown below.
If you have been successful so far, then congratulations. You are ready to get real now.
Fetching Real LetsEncrypt Wildcard Certificates using Traefik
Since our staging was successful, let us now open the /home/anand/docker/compose/udms/traefik.yml file and comment out the following line (as shown below) so that we can reach the real LetsEncrypt server for the DNS challenge.
# - --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
Save the Traefik docker-compose file.
Open the acme.json file, delete all contents, and save it. Alternatively, you may delete acme.json file and recreate an empty file.
Next, recreate Traefik and follow the logs once again to make sure everything goes smoothly (you can use the following 1-line command - change the username):
sudo docker compose -f /home/anand/docker/docker-compose-udms.yml up -d --force-recreate traefik; tail -f /home/anand/docker/logs/udms/traefik/traefik.log
The logs look good without any errors. Highlighted are some of the key messages to look for in the log. Now let us check the certificates in the browser to verify the SSL certificate.
Looks good. Let us also check the acme.json file.
Notice that it does not say "staging" anymore. So, all is good so far and our Docker Compose Traefik stack is coming along quite well.
Additional Middlewares and Chains
Now that everything is working great, let us start improving the Traefik setup using middlewares. We already added some security with the basic authentication middleware. Let's add a few more.
Each middleware is added as a separate file provider, which will then be added to the Docker compose labels of services.
Rate Limit
The rate limit middleware ensures that services will receive a fair number of requests. This is helpful if intentionally (eg. security breach) or unintentionally your services are being bombarded with requests causing a denial of service (DDoS) situation.
Create a new file called middlewares-rate-limit.yml in appdata/traefik3/rules/udms folder and add the following contents to it (pay attention to the spacing/formatting):
http: middlewares: middlewares-rate-limit: rateLimit: average: 100 burst: 50
Save and exit. Now we can add this middleware to Traefik 3 dashboard the same way we added the basic authentication middleware - by modifying the middlewares label in docker compose as follows:
# Middlewares - "traefik.http.routers.traefik-rtr.middlewares=middlewares-rate-limit@file,middlewares-basic-auth@file"
Note that I added rate-limiting as the first line of defense.
Security Headers
Next, let's add some security headers. Security headers are directives that shore up defenses in web browsers, making it harder to exploit any client-side vulnerabilities such as clickjacking.
Create a new file called middlewares-secure-headers.yml in appdata/traefik3/rules/udms folder and add the following contents to it (pay attention to the spacing/formatting):
http: middlewares: middlewares-secure-headers: headers: accessControlAllowMethods: - GET - OPTIONS - PUT accessControlMaxAge: 100 hostsProxyHeaders: - "X-Forwarded-Host" stsSeconds: 63072000 stsIncludeSubdomains: true stsPreload: true # forceSTSHeader: true # This is a good thing but it can be tricky. Enable after everything works. customFrameOptionsValue: SAMEORIGIN # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options contentTypeNosniff: true browserXssFilter: true referrerPolicy: "same-origin" permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=(), usb=(), vr=()" customResponseHeaders: X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex," # disable search engines from indexing home server server: "" # hide server info from visitors
Adding security headers via a middleware file simplifies the Traefik docker-compose file.
As with other middlewares provided in the file, we now have to include this middleware in the compose file by modifying the line as follows:
# Middlewares - "traefik.http.routers.traefik-rtr.middlewares=middlewares-rate-limit@file,middlewares-secure-headers@file,middlewares-basic-auth@file"
As always, recreate the services after any changes to the docker-compose file and test to ensure everything works before adding new middlewares.
That's it. All of our middlewares are now added.
Now would be a good point to recreate Docker Compose Traefik service and make sure that the logs have no errors and the dashboard is accessible.
Middleware Chains
There is a simpler way to provide middlewares than specifying each of them individually. We can create what is known as "middleware chains". A Chain is simply a group of middlewares.
Let's create two chains:
- chain-no-auth: Middlewares chain to use when no authentication layer is needed. May be the app has strong built in multifactor authentication already or may be authentical interferes connectivity from devices (e.g. Plex clients). For these, we are only specifying middleware for rate limit and security headers.
- chain-basic-auth: Middlewares chain to use when basic authentication is needed. We are including the following middlewares in order rate limit, security headers, and basic HTTP authentication.
Let's create the following file providers in appdata/traefik3/rules/udms for each chain.
chain-no-auth.yml
http: middlewares: chain-no-auth: chain: middlewares: - middlewares-rate-limit - middlewares-secure-headers
chain-basic-auth.yml
http: middlewares: chain-basic-auth: chain: middlewares: - middlewares-rate-limit - middlewares-secure-headers - middlewares-basic-auth
With these two middleware chains defined, now we can modify the middlewares label in Traefik v3 docker compose as follows:
# Middlewares - "traefik.http.routers.traefik-rtr.middlewares=chain-basic-auth@file"
Instead of the long:
# Middlewares - "traefik.http.routers.traefik-rtr.middlewares=middlewares-rate-limit@file,middlewares-secure-headers@file,middlewares-basic-auth@file"
As evident, middleware chains further simplify docker-compose files. You can call these chains on any container and they will use the same middleware configuration.
At this point, the final Traefik v3 Docker Compose file should look like what was shown previously.
Be the 1 in 200,000. Help us sustain what we do.You will gain benefits such as Deployarr access, discord roles, exclusive content, ad-free browsing, and more.🔥 Holiday Sale! 25% Off Platinum Membership$399.99$299.99 (ends December 31).Join the Geek Army (starting from just $1.67/month)
Putting Apps Behind Traefik Proxy
Now that our Traefik 3 and Basic Authentication are up and running, let us start adding some apps. Previously in this series of tutorials, in the Docker server guide, we added several apps. Let's take a few of those and put them behind Traefik.
I am not going to show you how to install each app. Rather, this guide will focus on how to put them behind Traefik.
Once you read and understand this guide, it should be quite simple to use the docker-compose snippets from my GitHub Repo to set up the other apps that you are interested in.
Portainer
In the Docker server guide, we added Portainer (compose/portainer.yml file) using the following Docker Compose:
services: # Portainer - WebUI for Containers portainer: container_name: portainer image: portainer/portainer-ce:latest # Use portainer-ee if you have a Business Edition license key security_opt: - no-new-privileges:true restart: unless-stopped # profiles: ["core", "all"] networks: - socket_proxy # command: -H unix:///var/run/docker.sock # # Use Docker Socket Proxy instead for improved security command: -H tcp://socket-proxy:2375 ports: - "9000:9000" volumes: # - /var/run/docker.sock:/var/run/docker.sock:ro # # Use Docker Socket Proxy instead for improved security - $DOCKERDIR/appdata/portainer/data:/data # Change to local directory if you want to save/transfer config locally environment: - TZ=$TZ
Portainer Traefik v3 Docker Labels
Now let's add Docker labels to put portainer behind Traefik. The labels should look slightly different from what we did for Traefik Dashboard above. Here is the modified Docker Compose for Portainer:
services: # Portainer - WebUI for Containers portainer: container_name: portainer image: portainer/portainer-ce:latest # Use portainer-ee if you have a Business Edition license key security_opt: - no-new-privileges:true restart: unless-stopped # profiles: ["core", "all"] networks: - t3_proxy - socket_proxy # command: -H unix:///var/run/docker.sock # # Use Docker Socket Proxy instead for improved security command: -H tcp://socket-proxy:2375 ports: - "9000:9000" volumes: # - /var/run/docker.sock:/var/run/docker.sock:ro # # Use Docker Socket Proxy instead for improved security - $DOCKERDIR/appdata/portainer/data:/data # Change to local directory if you want to save/transfer config locally environment: - TZ=$TZ labels: - "traefik.enable=true" # HTTP Routers - "traefik.http.routers.portainer-rtr.entrypoints=websecure" - "traefik.http.routers.portainer-rtr.rule=Host(`portainer.$DOMAINNAME_1`)" # Middlewares - "traefik.http.routers.portainer-rtr.middlewares=chain-no-auth@file" # HTTP Services - "traefik.http.routers.portainer-rtr.service=portainer-svc" - "traefik.http.services.portainer-svc.loadbalancer.server.port=9000"
Here is the summary of the modifications.
- networks: We added Portainer to t3_proxy network. This is needed for all services that you want to put behind Traefik, using Docker labels. So Portainer is now part of both socket_proxy and t3_proxy network.
- ports: Optionally, we could comment out the ports section if we do not want access to Portainer using http://DOCKER-HOST-IP:9000 as it should be available using the FQDN (https://portainer.simplehomelab.com).
- Just as with Traefik dashboard, we are specifying Routers, Middlewares, and Services, with some minor changes.
- We are putting Portainer behind chain-no-auth middleware because Portainer does not play well with Basic HTTP Authentication (based on my experience). Plus, it has built-in authentication. Personally, I have either Google OAuth or Authelia for all my services, including Portainer. I recommend that eventually you add one of those authentication layers.
- Unlike Traefik dashboard, which is unique, for most services we will provide the port number (inside the container) on which the app is listening. Portainer listens on port 9000. So we point portainer-rtr.service to a service name portainer-svc and in the next line, we define where that service is listening at (portainer-svc.loadbalancer.server.port=9000.
This is normally how most services are put behind Traefik. So, take note of the Docker labels above. When in doubt you can always check my Github repo for examples for over 100 apps.
Recreate the stack and Portainer WebUI should be available at https://portainer.simplehomelab.com.
Traefik 3 seems to be using the correct SSL certificates.
Homepage
In the Docker media server guide, we added Homepage Docker Compose. To put Homepage behind Traefik, here is the modified Homepage Docker Compose:
services: # Homepage - Application Dashboard homepage: image: ghcr.io/gethomepage/homepage:latest container_name: homepage security_opt: - no-new-privileges:true restart: unless-stopped # profiles: ["apps", "all"] networks: - t3_proxy - socket_proxy ports: - "3000:3000" volumes: - $DOCKERDIR/appdata/homepage:/app/config environment: TZ: $TZ PUID: $PUID PGID: $PGID labels: - "traefik.enable=true" # HTTP Routers - "traefik.http.routers.homepage-rtr.entrypoints=websecure" - "traefik.http.routers.homepage-rtr.rule=Host(`$DOMAINNAME_1`) || Host(`www.$DOMAINNAME_1`)" # Both domain.com and www.domain.com # Middlewares - "traefik.http.routers.homepage-rtr.middlewares=chain-basic-auth@file" # HTTP Services - "traefik.http.routers.homepage-rtr.service=homepage-svc" - "traefik.http.services.homepage-svc.loadbalancer.server.port=3000"
Here is the summary of the modifications.
- networks: We added Homepage to t3_proxy network so we can put it behind Traefik Proxy.
- ports: Optionally, we could comment out the ports section if we do not want access to Homepage using http://DOCKER-HOST-IP:3000 as it should be available using the FQDN.
- Just as with Portainer dashboard, we are specifying Routers, Middlewares, and Services, with some minor changes.
- Notice - "traefik.http.routers.homepage-rtr.rule=Host(`$DOMAINNAME_1`) || Host(`www.$DOMAINNAME_1`)". We are setting up Homepage to be served on the root domain (simplehomelab.com or www.simplehomelab.com). So, Homepage will be the face of the domain. From there, we have links to all other apps.
- Unlike Portainer, we are putting Homepage behind chain-basic-auth middleware to add authentication layer.
- Homepage listens on port 3000. So, we point homepage-rtr.service to a service name homepage-svc and in the next line, we define where that service is listening at (homepage-svc.loadbalancer.server.port=3000.
Guacamole
Apache Guacamole is a clientless remote desktop gateway. It supports standard protocols like VNC, RDP, and SSH. It needs no plugins or software and works from any modern browser. In my opinion, it is a must-have for admins. [Read: Install Guacamole on Docker – VNC, SSH, SFTP, and RDP like a Boss!]
It is not easy to set up Guacamole as an app on host systems. But Docker has made this very easy. We have published a detailed Guacamole Docker Compose guide. But I will include just a few labels here as it is a special case example.
.... # Middlewares - "traefik.http.routers.guacamole-rtr.middlewares=chain-basic-auth@file,add-guacamole" - "traefik.http.middlewares.add-guacamole.addPrefix.prefix=/guacamole" ...
The main reason I include Guacamole as an example is to showcase the addPrefix.prefix middleware. If I visit guac.simplehomelab.com, it will take me to the page shown below, which is a generic page with some information and not the Guacamole login page I want to reach.
The login page is available at guac.simplehomelab.com/guacamole. Instead of typing /guacamole manually I can define as addPrefix.prefix middleware:
- "traefik.http.middlewares.add-guacamole.addPrefix.prefix=/guacamole"
Then you can activate this middleware by adding add-guacamole to the middlewares label as shown in the Guacamole docker-compose example above. This causes guac.example.com/guacamole to be served through guac.example.com.
Another app that benefits from the addPrefix.prefix middleware is PiHole (see my GithHub). However, it does not work for all apps. For example, this does not work for ZoneMinder, which needs /zm/ prefix. [Read: ZoneMinder Docker Guide for Beginners: Best Free Video Surveillance]
Apps with Self-Signed Certificates - Nextcloud, Proxmox, Unifi, etc.
While Traefik communicates externally to clients using the LetsEncrypt cert, it communicates to services on the back-end using HTTP. But, some apps are only accessible using HTTP protocol with typically a self-signed certificate.
In the past, I have recommended enabling insecureSkipVerify=true option to enable Traefik to work with apps that have a self-signed SSL certificate that is not trusted by browsers. But we want to keep our reverse proxy secure, so let’s find another way.
All of the configurations that we’ve worked with so far have been for Traefik's "HTTP" routers, but Traefik v3 also offers the option to configure TCP routers. Traefik’s TCP service has a pass-through option that will allow us to "pass-through" our secure connection to Nextcloud, Unifi Cnotroller, Proxmox VE Web Interface, etc.
I have already done a detailed post on putting Proxmox web interface behind Traefik, which should apply to any app with a built in self-signed SSL certificate.
Other Apps
There are several more apps in our Docker Traefik 3 server setup. The above examples show most of the possibilities.
For docker-compose examples for over 100 apps, check my current GitHub Repo. The repo includes apps such as Radarr, Sonarr, Lidarr, SABnzbd, and more.
Usenet is Better Than Torrents:
For apps like Sonarr, Radarr, SickRage, and CouchPotato, Usenet is better than Torrents. Unlimited plans from Newshosting (US Servers), Eweka (EU Servers), or UsenetServer, which offer >3000 days retention, SSL for privacy, and VPN for anonymity, are better for HD content.
Cloudflare Cache and Media Servers
If you access your media servers through a proxy-enabled (orange-cloud) CNAME (e.g. https://plex.simplehomeab.com), then, turn off Cloudflare caching using page rules. It is against Cloudflare ToS to pass media through their caching system. Your account will be disabled.
There is a limit of 3 page rules on free accounts. For this reason, I prefix my media server CNAMEs with a common string (e.g. proxair.domain.com, proxplex.domain.com, and proxjf.domain.com). Now I can use wildcardd page rule (https://prox*.domain.com/*) and disable caching for all these subdomains.
You could use whatever prefix (e.g. docker, my, etc.) you prefer as long as the prefix is not part of any other CNAMEs. This applies to all media servers discussed in this guide: Plex, Jellyfin, and Airsonic.
Adding non-Docker or External Apps Behind Traefik
We have Traefik v3 docker compose working well for all of our services within our Docker network, but what about connecting to other hosts outside of Docker? For example, Home Assistant, PiHole on a separate Raspberry pi, etc. Traefik can also reverse proxy them and it is easy to implement.
Traefik’s File provider allows us to add dynamic routers, middlewares, and services. Earlier we only used our rules directory to add middlewares, but we can easily add an external host by adding a new file to this directory. Traefik will auto-detect and update its configurations.
For this example, we will create a new route to an external AdGuard Home on Raspberry Pi. You will find many other examples in my Github Repo.
This process is exactly the same as what is explained in the video below:
Create a file called app-adguard-home.yml in the appdata/traefik3/rules/udms folder and add the following content to it.
http: routers: adguard-rtr: rule: "Host(`ag.{{env "DOMAINNAME_1"}}`)" entryPoints: - websecure middlewares: - chain-no-auth service: adguard-svc tls: certResolver: dns-cloudflare options: tls-opts@file services: adguard-svc: loadBalancer: servers: - url: "http://192.168.1.225:80"
Essentially, what is shown above is the same as what we saw presented in Docker labels for other services. Here, we are using the file provider in YAML format. Here is an explanation of the above file provider:
Additional Readings
This guide is already too long. There are several special case scenarios that cannot be covered in this guide. I will publish separate guides on those topics. When I do, I will list them here for further reading:
- How to put Proxmox, Unifi, Nextcloud, etc. behind Traefik?
- Traefik Auth Bypass: Conditionally Bypassing Forward Authentication
- Multiple Traefik Instances on Different Domains/Hosts and One External IP
- Adding multiple domains [planned]
- Traefik Plugins [planned]
Troubleshooting
Where appropriate I have already pointed out a few areas where new users make mistakes frequently.
Here are some issues either I ran into or others have mentioned. If none of the solutions listed below works, then feel free to join our Discord Server and ask for help.
Learn to Read Logs
I repeat, learn how to read Traefik logs. This will be your best friend.
tail -f /home/anand/docker/logs/udms/traefik/traefik.log
Here are some of the problems you can pick up from the logs:
- Incorrect Cloudflare API Token
- Incorrect middleware name
- LetsEncrypt rate limit
- DNS TXT missing/not found during ACME DNS challenge
- Incorrect acme.json permissions
- Incorrect Docker labels - service pointing to a wrong port number (frequently overlooked when copy-pasting)
- Missing file providers
Typos and Misnaming
Typos and misnaming are two of the most common mistakes people make. For example, when I was creating Portainer, I had a typo in a middleware name. This caused Portainer not to start.
Checking Traefik 3 logs can help you figure out what the problem is. In addition, check Traefik 3 dashboard for more information (see below).
In the case of Portainer, upon digging deeper, Traefik 3 dashboard revealed that Traefik 3 could not find the middlewares.chain-no-auth.
This should have been chain-no-auth. Once I fixed this issue, Portainer started right up.
404 or Cloudflare 502
The most common reason for these errors is:
- The service did not start properly
- The service port defined under Traefik v3 Docker labels is wrong
For 1) check the logs for service to ensure it starts without problems. Sometimes improper permissions for the app data folder can cause the service to not start. If the service started without problems, then check that you specified the right port for the service under Traefik labels (commonly overlooked during copy-pasting).
Cloudflare 504 Error
Cloudflare 504 error can be solved by adding the following to CLI commands on traefik v3 docker compose:
- --entrypoints.websecure.http.tls=true
Cloudflare 522 Error - Connection Timed Out
This means that cloudflare can't even reach your Traefik host. This is much worse than a 404 or a Cloudflare 502, where Cloudflare could get in but just not find your service. The most common cause for the 522 error is probably a firewall (e.g. Router, UFW, etc.) blocking (or not allowing) ports 80 and 443, which are needed for Traefik to work. This includes missing or improper port forwarding ports 80 and 443 to the Traefik host.
Internal Server Error
This most commonly occurs when the app that we are trying put behind Traefik is on HTTP protocol with a self-signed certificate. Check additional readings section for how to deal with such apps (e.g. Proxmox, Unifi, Nextcloud, etc.).
CG-NAT
Some mobile internet providers issue a shared IP and you may be behind CT-NAT. This will interfere with Traefik as ports 80 and 443 cannot reach the Docker Host from the internet. You can pay for a dedicated IP in this case, or take a look at alternatives such as Cloudflare Tunnels/Access.
Tip to Identify Copy-Paste Errors
Another tip is to use one of the many available "Diff Checkers" online. Copy your code on one side and copy-paste the docker-compose examples from this guide or my Github repo on the other side. The differences between the two will be highlighted for you.
Unable to access using domain names from the LAN (but works from outside the LAN network)
If you use a firewall (e.g. OPNsense), try adding a host override as shown in the picture below. In addition, look into implementing DNS hairpinning in your environment.
Docker Compose Traefik v3 Stack - Final Thoughts
This Traefik reverse proxy Docker guide is an addon guide to my Docker media server guide and is an upgraded version of the guide previously published in 2024, 2022, 2020, and 2018. As you can see it turned out to be a lengthy one. But it provides a one-stop solution for implementing Traefik 3 reverse proxy for Docker services.
As mentioned before, one of the main advantages of a reverse proxy is the ability to expose the app to the internet without exposing its ports. If you have successfully implemented reverse proxy for docker then at this point I strongly recommend disabling port forwards on your router (except 80 and 443 that Traefik needs). You would still be able to access your apps using IP-ADDRESS:port from your home network.
Please remember that this is just the start of the journey. I would at the very least suggest a strong Authentication system. In addition, a strong intrusion prevention system with CrowdSec is highly recommended.
Hopefully, this guide broke Traefik 3 concepts down enough for you to understand. I am still learning and by no means an expert on this topic. So, if you have a better way of doing what I did, please feel free to share in the comments and I will respond and update the guide if needed. Or, hop on to our Discord server to chat about it.
Otherwise, we hope that this Traefik 3 Docker Home Server setup tutorial guide helped you accomplish what you set out to do.