Ultimate Traefik Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

This revised, 2024, Traefik Docker Compose is the most in-depth, step-by-step, guide on the planet. period. Everything from beginning to end in detail.

It has been over six years since I published my first Traefik guide, and then updated versions in 2020 and 2022.

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

These have helped hundreds of thousands of people. To go along with this guide, I also published my setup on GitHub.

Note: If you prefer the convenience of automating everything presented in this guide + more (e.g. Authelia, Backups, Portainer, Homepage, etc.), then check out Auto-Traefik Script.

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 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:
  1. Ultimate Docker Server: Getting Started with OS Preparation [VIDEO] [2024]
  2. Docker Media Server Ubuntu/Debian with 60+ Awesome Apps [VIDEO] [2024]
  3. ZeroTier VPN Ubuntu, Docker, Synology, Windows: Secure on-the-go access [coming soon]
  4. Nginx Proxy Manager Docker Compose Guide: Simplest Reverse Proxy [coming soon]
  5. Traefik Reverse Proxy
  6. Authelia Docker Compose Guide: Secure 2-Factor Authentication [2024]
  7. Google OAuth Docker Compose Guide: Multi-Factor Authentication [2024]
  8. Docker Security Practices for Homelab: Secrets, Firewall, and more
  9. Cloudflare Settings for Docker Traefik Stacks
  10. Implementing a Backup System for Docker Traefik Stack [coming soon]
  11. Automate the Whole Process with Auto-Traefik Script
It is best to follow the series from the beginning so you not only understand WHAT to do but also WHY we do it.

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 Docker-Compose stack.

Table of Contents

Traefik Docker Server Guide

Here are the previous versions of this guide:

Note: My setup changes constantly and evolves. It is nearly impossible to keep this guide synced with my latest updates. Therefore, I strongly suggest that you use this guide to get started and use my GitHub repo and the detailed commit notes I publish, for changes, improvements, inspiration, and more.

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.

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:

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

My Topton V700 Minipc From Aliexpress - Proxmox Server
Topton V700 Minipc From Aliexpress - $481 - Proxmox Server
If you are ordering from AliExpress, remember 3 things: 1) Check with the seller if the item is in stock first, 2) Pick only sellers with a very high rating (>95% at least), and 3) Be prepared for delays/extended delivery times (in my case the seller asked for extra shipping time due to availability).

Differences from Previous Traefik Docker Guide

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.

Objectives of this Traefik 2 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:

Playlist: Auto Traefik 2 - Docker, Traefik, SSL, Authelia, and more in minutes
Watch this playlist on YouTube

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.
56 / 150 by Dec 31, 2024
You will gain benefits such as discord roles, exclusive content, ad-free browsing, raffles, and more.
Become a Sponsor (starting from just $1.67/month)

Traefik Setup: Pre-read

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

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 2

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.

Docker Traefik Guide - Reverse Proxy Schematic
Reverse Proxy Schematic

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:

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 2 Routers, Middlewares, and Services

Traefik v2 uses three major components in the configuration: Routers, Services, and Middlewares.

Traefik Provider
Traefik Providers Overview

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 v2. 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 2 Configuration

There are multiple ways to configure Traefik 2 and this can be quite overwhelming to newbies. Even with some experience, I find it difficult to wrap my head around Traefik 2 configuration methods at times.

So, let's spend time understanding some important Traefik 2 configuration details: static vs dynamic configuration.

Static And Dynamic Configurations For Traefik V2
Traefik 2 Configuration Overview

Dynamic Configuration

Traefik is a dynamic reverse proxy, meaning it can add and remove routes automatically when containers start or stop.

In simple terms, Dynamic configuration does not require Traefik to be restarted for the changes to take effect.

Dynamic configuration can be specified in two places:

  1. With the Docker provider by using labels in the Docker Compose for each service.
  2. 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.

Changes to static configurations require Traefik to be restarted to take effect.

There are three different ways to define the static configuration for Traefik 2. Only one can be used at the same time.

  1. Configuration file - can be in a TOML or YAML file (e.g. traefik.toml or traefik.yml).
  2. Command-line (CLI) arguments - these arguments are passed during docker run (this is what we will be using).
  3. As environment variables - here is a list of all environmental variables.

These ways are evaluated in the order listed above.

Getting Started with Traefik v2

Here are a few tips that I recommend while setting up Traefik 2.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 Docker Compose stack.

Prep Work for Traefik 2 Setup

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

Next, let’s do some prep work to get the Traefik v2 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:

  1. 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.
  2. 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.
  3. 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.
    Cloudflare Dns Records A And Cname Records
    Cloudflare Dns Records A And Cname Records
    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.
  4. Cloudflare SSL Settings - Full SSL
  5. Cloudflare Ssl Encryption Mode
    Cloudflare Ssl Encryption Mode
  6. 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 Docker Compose guide.

Performing all the Cloudflare checks, port-forwarding checks, firewall checks, etc. take just a few seconds with Auto-Traefik script. It is a FREE feature for anyone to use.

Take a look:

Auto Traefik 2 (Parts 2-3) - Must Read Info and Checks

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.

Docker Root Folder And Files
Docker Root Folder And Files

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

Basic HTTP Authentication is a good start, but for a more secure authentication layer, consider switching to Google OAuth or Authelia later, which support multi-factor authentication.

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
Note that htpasswd command requires basic packages to be installed, in case you skipped the previous parts of this series.

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:

  1. username: with your HTTP username.
  2. mystrongpassword: with your hashed HTTP password generated using the link above.
Note that if you put the username and password in Docker compose file directly instead of in the environment file, then any $ signs in the hashed password must be escaped by adding another $ signed in front. For example:

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):

  1. Cloudflare Email + Global API Key
  2. Cloudflare Scoped DNS API Token
  3. Cloudflare DNS API Token + Zone API Token
Let'S Encrypt Traefik Wildcard Dns Providers List
Let's Encrypt Traefik Wildcard Dns Providers List

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.

In the previous versions of this guide, I described using Cloudflare Email and Global API Key. Using a scoped DNS API token is more secure.

On your Cloudflare account, go to Profile->API Tokens, and then create a new "Custom" token as shown below.

Cloudflare Scoped Dns Api Token For Traefik
Cloudflare Scoped Dns Api Token For Traefik

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:

Cf_Dns_Api_Token Docker Secret
Cf_Dns_Api_Token Docker Secret

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.

Note that the CF_DNS_API_TOKEN can read all your zones and edit DNS records for all zones. This is better than using Email and Global API key. But you can make it even more restrictive as described here. Create CF_ZONE_API_TOKEN with Zone/Zone/Read all zones and CF_DNS_API_TOKEN with Zone/DNS/Edit but only the specific zone you are going to use Traefik with.

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 2 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 traefik2
mkdir traefik2/acme
mkdir traefik2/rules
mkdir traefik2/rules/udms

traefik2 is the folder we will use to store all Traefik 2.0 related configurations. udms is the hostname of our ultimate docker media server (udms), we set in the previous Docker guide.

Reminder: I sync all my Docker configurations across my 5 Docker hosts to make it 1) easier to push all changes to my Github repo and 2) have a backup of Docker configurations from one host on all other hosts. Hence, in my Github repo, you will see hostname suffixes/prefixes/folders (e.g. hs, ws, ds918, mds, dns, etc.) wherever applicable.

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/traefik2/acme folder using the following command

touch acme.json

Set proper permission for acme.json file using the following command (from inside appdata/traefik2/acme):

chmod 600 acme.json
CAUTION: Without 600 permissions on acme.json, Traefik won't start. This is one of the most commonly overlooked steps.

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.

Performing all of the above prep-work takes just a few seconds with Auto-Traefik script. Take a look:

Auto Traefik 2 (Part 5) - Traefik Staging and Production Setup

Traefik 2 Docker Compose

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

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.

Caution: Pay attention to blank spaces in the docker-compose examples below. Spacing is very important for YAML to read your files correctly.

At the end of the Docker server guide, our docker-compose-udms.yml file started like this:

Update (March 21, 2024): The version tag at the top of Docker compose file is now obsolete and throws a warning. This has been removed from my guides.
########################### 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.

Note: Any line with # in front is a comment and is ignored by Docker Compose.

Define Network for Traefik 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
  t2_proxy:
    name: t2_proxy
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.90.0/24
Note that in the previous versions of my guide, I used t2_proxy as the network name for Traefik (for version 2). You may find remnants of this name in some of my older guides and in my Github repo.

All services behind Traefik Proxy network will use IP addresses between 192.168.90.1 and 192.168.90.254.

Note that 192.168.90.0/24 and 192.168.91.0/24 are random subnets I picked for Traefik and Socket Proxy. They have nothing to do with what your LAN subnet is. In fact, they cannot be the same as your LAN subnet. 192.168.90.0/24 and 192.168.91.0/24 are used only within the Docker environment and typically, you would never have to use or remember IPs in this subnet. For most users, what I have provided above should work 'as-is'.

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
  t2_proxy:
    name: t2_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
If you followed my Docker media server guide, you may have other Docker secrets already defined (e.g. plex_claim and mysql_root_password). If so, just add the new ones right below the existing ones.

The final 2 steps involve using the secrets inside Traefik 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
  t2_proxy:
    name: t2_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.

Note: Note that everything within include should be indented by two blank spaces.

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.

I am going to assume you already have Socket Proxy installed based on my Docker Server guide. If you don't then either 1) install Socket Proxy or 2) comment out the last line below that includes socket-proxy.yml
########################### NETWORKS
networks:
  default:
    driver: bridge
  socket_proxy:
    name: socket_proxy
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.91.0/24
  t2_proxy:
    name: t2_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 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).

Traefik Docker compose shown below might differ slightly from what is in my GitHub repo. I have removed some sections that are not needed for this guide (e.g. healthchecks, metrics export with prometheus/influxdb, etc.).
services:
  # Traefik 2 - Reverse Proxy
  traefik:
    container_name: traefik
    image: traefik:2.10
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
    # profiles: ["core", "all"]
    networks:
      t2_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=t2_proxy 
      - --providers.docker.swarmMode=false
      - --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/traefik2/rules/$HOSTNAME:/rules # Dynamic File Provider directory
      # - /var/run/docker.sock:/var/run/docker.sock:ro # Enable if not using Socket Proxy
      - $DOCKERDIR/appdata/traefik2/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
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.

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.
56 / 150 by Dec 31, 2024
You will gain benefits such as discord roles, exclusive content, ad-free browsing, raffles, and more.
Become a Sponsor (starting from just $1.67/month)

Customizing Traefik Docker Compose

That was a long Docker Compose for Traefik. Let's breakdown.

Traefik Image and Restart Policy

  # Traefik 2 - Reverse Proxy
  traefik:
    container_name: traefik
    image: traefik:2.10
    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).

In the past, certain Traefik versions included breaking changes, causing unexpected service outages. Therefore, I recommend you to use specific version tags instead of the latest image tag. Every few months, I test new versions and upgrade.

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:
      t2_proxy:
        ipv4_address: 192.168.90.254 # You can specify a static IP
      socket_proxy:

We are attaching the Traefik container to two networks: t2_proxy and socket_proxy. All other services that are also attached to t2_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.

Rarely is setting a static IP needed. If two containers are on the same network (e.g. t2_proxy) they can see each other using their hostname (e.g. socket-proxy:2375 instead of CONTAINER-IP:2375, as you will see later in the guide) and so knowing the IP is not necessary.

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

You cannot mix and match methods. YML/TOML will take precedence over the CLI commands.

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.

One reason I am sticking CLI commands at this point is that Go templating only works with dynamic configuration. This means I have to hard-code domain name, Cloudflare credentials, etc. directly in the traefik.yml static configuration file, which I do not want to do.

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=t2_proxy 
      - --providers.docker.swarmMode=false

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 t2_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/traefik2/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 2 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 2 tutorial.

You can also provide dynamic configuration using a rules file (not discussed in this Traefik guide). But I like to keep all the rules separate with a clear name.
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 want Traefik to get wildcard Lets Encrypt SSL certificates (*.domain.com), then ACME DNS challenge is the only way to validate your domain.

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 2 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 2 get the real LetsEncrypt SSL certificates from the real server.

Traefik 2 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:

Restricted Content

Additional explanations and bonus content are available exclusively for the following members only:

Silver - Monthly, Silver - Yearly, Gold - Monthly, Gold - Yearly, Diamond - Monthly, Diamond - Yearly, and Platinum Lifetime (All-Inclusive)

Please support us by becoming a member to unlock the content.
Join Now


This will show all ports on which some service is listening, as shown below:
Listening Ports
Listening Ports - Already Occupied

Traefik 2 Volumes

Here are the volumes that I have specified in my Traefik 2 docker-compose file:

    volumes:
      - $DOCKERDIR/appdata/traefik2/rules/$HOSTNAME:/rules # Dynamic File Provider directory
      # - /var/run/docker.sock:/var/run/docker.sock:ro # Enable if not using Socket Proxy
      - $DOCKERDIR/appdata/traefik2/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 2 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 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.

Why are CF_DNS_API_TOKEN_FILE and HTPASSWD_FILE point to their respective secret files in /run/secrets folder? And, why do those variables have a _FILE suffix?

Restricted Content

Additional explanations and bonus content are available exclusively for the following members only:

Silver - Monthly, Silver - Yearly, Gold - Monthly, Gold - Yearly, Diamond - Monthly, Diamond - Yearly, and Platinum Lifetime (All-Inclusive)

Please support us by becoming a member to unlock the content.
Join Now

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

The rule has to follow Go formatting, which means that we need to use backticks around string values, not apostrophes!

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.

Pay attention to these router names. I have had many followers copy-paste Docker labels from one service to another but forget to change the router name. This causes conflicts and unexpected behavior.

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

Remember the 3 Traefik components: Routers, Services, and Middlwares? Middlewares are used to modify the behavior of the route, in this case inserting authentication between the entrypoint and the service.
Middlewares - Basic HTTP Authentication
Note: In the previous version of this Traefik Docker Compose tutorial, I used TOML. However, I have since switched to YAML (or YML), which is much cleaner and simpler. In my GitHub repo, you will find both TOML and YAML examples. But note that only the YAML files are continually tested and updated.

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 2 rules folder ($DOCKERDIR/appdata/traefik2/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 2 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.

Reminder that all secrets are loaded under /run/secrets folder inside the container.

Now let us add this middleware to our Traefik 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.

Using the @ symbol to specify the provider is a new feature in Traefik v2, but it is required to identify the source of the middleware. If no provider is specified, then Traefik assumes the source of the middleware is the current provider (i.e. @docker).

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

Traefik Docker Compose - Middleware Names
Traefik Middleware Names

Add Traefik to the Docker Stack

We have now built our Traefik 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
  t2_proxy:
    name: t2_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 2 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:

Simplify Docker, Docker Compose, and Linux Commands with Bash Aliases

sudo docker compose -f /home/anand/docker/docker-compose-udms.yml up -d
If you copy-paste Docker Compose snippets from my repository, do not forget to comment out profiles. If you do not, you will see the "no service selected" error as shown below.

No Service Selected
No Service Selected

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
In my previous guides, we used sudo docker logs -tf --tail="50" traefik or sudo docker compose -f /home/anand/docker/docker-compose-udms.yml logs -tf --tail="50" traefik to follow Traefik container logs.

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:

Restricted Content

Additional explanations and bonus content are available exclusively for the following members only:

Silver - Monthly, Silver - Yearly, Gold - Monthly, Gold - Yearly, Diamond - Monthly, Diamond - Yearly, and Platinum Lifetime (All-Inclusive)

Please support us by becoming a member to unlock the content.
Join Now

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 2 is configured correctly and succeeded in obtaining the LetsEncrypt certificate. I will show you two of them here.

Let's open Traefik 2 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 2 from the LetsEncrypt staging server.

Traefik Staging Certificate
Traefik Staging Certificate

Alternatively, you can open acme.json file located inside the appdata/traefik2/acme folder and look for signs of successful validation, as shown below.

Acme.json File For Staged Traefik 2.0 Letsencrypt Domain Validation
Acme.json File For Staged Traefik 2.0 Letsencrypt Domain Validation

If you have been successful so far, then congratulations. You are ready to get real now.

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.

Fetching Real LetsEncrypt Wildcard Certificates using Traefik

Since our staging was successful, let us now open the docker-compose-udms.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.

If you delete and recreate acme.json be sure to run sudo chmod 600 acme.json to set the right permissions.

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
Dns Challenge Traefik 2 Logs Showing Successful Certificate Retrieval From Letsencrypt
Dns Challenge Traefik 2 Logs Showing Successful Certificate Retrieval From Letsencrypt

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.

Final Letsencrypt Ssl Certificate Using Traefik Reverse Proxy
Final Letsencrypt Ssl Certificate Using Traefik Reverse Proxy

Looks good. Let us also check the acme.json file.

Acme.json File For Final Traefik 2.0 Letsencrypt Domain Validation
Acme.json File For Final Traefik 2.0 Letsencrypt Domain Validation

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.

All of what is explained above is exactly what Auto-Traefik but in a matter of seconds. Take a look:

Auto Traefik 2 (Part 5) - Traefik Staging and Production Setup

Additional Middlewares and Chains

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

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/traefik2/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 2 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.

Using the File provider for middlewares significantly reduces the size of docker-compose files by avoiding repetitions and reusing code.

Create a new file called middlewares-secure-headers.yml in appdata/traefik2/rules/udms folder and add the following contents to it (pay attention to the spacing/formatting):

Explaining what these security headers do is outside the scope of this post, which is already turning out to be one of the longest posts I have ever written. You can read more about traefik security headers here. What is provided here should work for most of you and most services.
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.

Compression

Lastly, we are going to add one more middleware to compress the output to reduce bandwidth and increase speed. Once again, create a new file called middlewares-compress.yml in appdata/traefik2/rules/udms folder and add the following contents to it (pay attention to the spacing/formatting):

http:
  middlewares:
    middlewares-compress:
      compress: {}

Next, let us enable this middleware by adding it to our middleware definition in compose:

      # Middlewares
      - "traefik.http.routers.traefik-rtr.middlewares=middlewares-rate-limit@file,middlewares-secure-headers@file,middlewares-basic-auth@file,middlewares-compress@file" 

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.

In my Github repo, you may find several other middlewares specified (e.g. CrowdSec). For now, these should get you started. As you add additional features to your stack (e.g. Authelia, CrowdSec, Google OAuth, Traefik Plugins, etc.), your middlewares chain will get longer.

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, security headers, and compression.
  • chain-basic-auth: Middlewares chain to use when basic authentication is needed. We are including the following middlewares in order rate limit, security headers, basic HTTP authentication, and compression.

Let's create the following file providers in appdata/traefik2/rules/udms for each chain.

chain-no-auth.yml

http:
  middlewares:
    chain-no-auth:
      chain:
        middlewares:
          - middlewares-rate-limit
          - middlewares-secure-headers
          - middlewares-compress

chain-basic-auth.yml

http:
  middlewares:
    chain-basic-auth:
      chain:
        middlewares:
          - middlewares-rate-limit
          - middlewares-secure-headers
          - middlewares-basic-auth
          - middlewares-compress
Note that all the middlewares would only apply when the service is accessed through the FQDN (fully qualified domain name; e.g. https://traefik.example.com).

With these two middleware chains defined, now we can modify the middlewares label in Traefik 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,middlewares-compress@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 Docker Compose file should look like what was shown previously.

Be the 1 in 200,000. Help us sustain what we do.
56 / 150 by Dec 31, 2024
You will gain benefits such as discord roles, exclusive content, ad-free browsing, raffles, and more.
Become a Sponsor (starting from just $1.67/month)

Putting Apps Behind Traefik Proxy

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

Now that our Traefik 2 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 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:
      - t2_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.

  1. networks: We added Portainer to t2_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 t2_proxy network.
  2. 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).
  3. Just as with Traefik dashboard, we are specifying Routers, Middlewares, and Services, with some minor changes.
  4. 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.
  5. 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.

Portainer With Traefik 2 Letsencrypt Wildcard Ssl Certificate
Portainer With Traefik 2 Letsencrypt Wildcard Ssl Certificate

Traefik 2 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:
      - t2_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`,`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.

  1. networks: We added Homepage to t2_proxy network so we can put it behind Traefik Proxy.
  2. 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.
  3. Just as with Portainer dashboard, we are specifying Routers, Middlewares, and Services, with some minor changes.
  4. Notice - "traefik.http.routers.homepage-rtr.rule=Host(`$DOMAINNAME_1`,`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.
  5. Unlike Portainer, we are putting Homepage behind chain-basic-auth middleware to add authentication layer.
  6. 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.

Guacamole Without Addprefix Traefik Middleware
Guacamole Without Addprefix Traefik Middleware

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

Cloudflare Page Rule To Bypass Cache For Media
Cloudflare Page Rule To Bypass Cache For Media

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

Auto Traefik 2 (Part 9) - Putting External Applications Behind Traefik

Create a file called app-adguard-home.yml in the appdata/traefik2/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:

Restricted Content

Additional explanations and bonus content are available exclusively for the following members only:

Silver - Monthly, Silver - Yearly, Gold - Monthly, Gold - Yearly, Diamond - Monthly, Diamond - Yearly, and Platinum Lifetime (All-Inclusive)

Please support us by becoming a member to unlock the content.
Join Now

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:

In addition, be sure to move on from Basic HTTP Authentication to a more modern authentication system such as Google Oauth or Authelia, which are also part of this Docker Traefik tutorial series.

Troubleshooting

Note: An updated version of this guide is available for Traefik v3: Ultimate Traefik v3 Docker Compose Guide [2024]: LE, SSL, Reverse Proxy

Where appropriate I have already pointed out a few areas where new users make mistakes frequently.

Auto-Traefik offers checks that anyone can use for free. Optionally, it automates everything in the proper way to avoid mistakes. And when it encounters errors during SSL certificate retrieval, it provides meaningful error messages to highlight where the problem might be: rate limit, DNS record problems, incorrect Cloudflare API token, etc.

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 2 logs can help you figure out what the problem is. In addition, check Traefik 2 dashboard for more information (see below).

Traefik V2 Router Error On Dashboard
Traefik V2 Router Error On Dashboard

In the case of Portainer, upon digging deeper, Traefik 2 dashboard revealed that Traefik 2 could not find the middlewares.chain-no-auth.

Traefik 2.0 Router Error Details
Traefik 2.0 Router Error Details

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:

  1. The service did not start properly
  2. The service port defined under Traefik 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 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.

Opnsense Host Override
Opnsense Host Override (Credit: @Twisted48 (On Discord Server)

Docker Compose Traefik v2 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 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 2 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 2 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 2 Docker Home Server setup tutorial guide helped you accomplish what you set out to do.

Be the 1 in 200,000. Help us sustain what we do.
56 / 150 by Dec 31, 2024
Join Us (starting from just $1.67/month)

Anand

Anand is a self-learned computer enthusiast, hopeless tinkerer (if it ain't broke, fix it), a part-time blogger, and a Scientist during the day. He has been blogging since 2010 on Linux, Ubuntu, Home/Media/File Servers, Smart Home Automation, and related HOW-TOs.