Ultimate Docker Home Server with Traefik 2, LE, and OAuth / Authelia [2020]

Traefik 2 reverse proxy with LetsEncrypt and OAuth for Docker services can be quite challenging. This in-depth docker tutorial will show you how to set up a Docker Home Server with Traefik 2, LetsEncrypt, and OAuth.

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

First of all, to the readers of our Docker media server, Traefik 1 Tutorial, and Traefik Google OAuth guides, I apologize for the delay. This guide is long overdue and I know thousands of you have been eargerly waiting for this update.

We are going to cover most of everything there is to setup a Docker Home Server with Traefik 2, LetsEncrypt SSL certificates, and Google OAuth for security. However, frequently, we will refer you back to my previous guides for some reading to not make this guide too lengthy.

This Traefik 2 Docker Home Server stack is a key component of my smart home setup as well as this website, which runs on WordPress on Docker. So without further delay, let's get going with the Traefik 2 Docker Home server guide.

Note: If you are looking for a simpler (than Traefik) reverse proxy with LetsEncrypt, check out Nginx Proxy Manager in our updated Docker media server guide: Docker Media Server Ubuntu: Compose for 23 Awesome Apps.

There is more than one way to skin this (Docker Traefik 2) cat and we are by no means an expert. As always, my objective is to share what I learn so others can successfully replicate what I did. If you have better ideas, feel free to share in the comments sections.

Table of Contents

My setup is constantly changing and evolving. It is practically not possible for me to keep this guide up-to-date with all the changes I am making. Therefore, you most probably will find differences between what is written in this guide and what is on my repository.

So I strongly recommend checking the Changelog on my GitHub repo to look at the recent changes and relevant explanations. Every few months, I will update this guide so it catches up to my GitHub repo. When I do, the changelog for this guide (see below) will be updated.

Changelog
  • July 14, 2020 - Introduced a new variable $DOCKERDIR, which is the docker root folder. All references to $USERDIR/docker changed to $DOCKERDIR.

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

Objectives of this Traefik 2 Docker Home Server Setup

My objectives for this setup remains pretty much the same as explained in my original Docker media server guide, with some minor changes.

One of the big tasks of a completely automated media server is media aggregation. For example, when a TV show episode becomes available, automatically download it, collect its poster, fanart, subtitle, etc., put them all in a folder of your choice (eg. inside your TV Shows folder), update your media library (eg. on Jellyfin, Emby, or Plex) and then send a notification to you (eg. Email, Mobile notification, etc.) saying your episode is ready to watch.

Sounds awesome right?

There are several apps that can do such tasks and we have compiled them in our list of best home server apps.

Here is a summary of some of the apps I wanted my Docker home server to run:

  • Frontends: Traefik, Portainer, Organizr, Heimdall
  • Smart Home: HA-Dockermon, Mosquitto MQTT Broker, ZoneMinder, MiFlora Plant Sensors
  • Databases: MariaDB, phpMyAdmin, InfluxDB, Postgres, Grafana
  • Downloaders: downloader, Transmission Bittorrent with VPN, SABnzbd, qBittorrent with VPN
  • Indexers: NZBHydra2, Jackett
  • PVRs: Lidarr, Radarr, Sonarr, LazyLibrarian
  • Media Servers: Airsonic, Plex, Emby, Jellyfin, Ombi, Tautulli, PhotoShow, Calibre and more
  • Media File Management: Bazarr, Picard, Handbrake, MKVToolNix, MakeMKV, FileBot, and more
  • System Utilities: Firefox, Glances, APCUPSD, Logarr, Guacamole, Dozzle, qDirStat, StatPing, SmokePing, and more.
  • Maintenance: Ouroboros and Docker-GC

I used to run Home Assistant on Docker too but have now moved to HASSio on Docker. For the up-to-date list of apps I run using my Docker Traefik 2 setup please refer to my GitHub Repo.

In addition to the objectives listed in my original Docker media server post, I want my Traefik 2 Docker Home Server setup to offer the following:

  • Provide database services for all other apps to use
  • Provide utilities such as VNC, RDP, SSH, remote file access, service monitoring, and more to simplify my life
  • Make all apps securely accessible over the internet without having to port-forward on the router
  • Offer an easy way to access the apps using user-friendly domain names instead of ports
  • Offer a higher level of security since the apps will be exposed to the internet

It may seem like a complex setup, but trust me, docker, with Docker Compose can make installation and maintenance of these home server apps easier. [Read: 9 Best music server software options: Build your own Spotify]

Requirements for this Docker Traefik 2 Setup

Several requirements must be met before proceeding with this Docker tutorial for setting up microservices behind Traefik reverse proxy.

1. Home Server

A Home Media Server is a server located in your home network that acts as a central data storage and serving device. Typically, a home server is always on, has tons of storage capacity and ready to serve files (including media) when the need arises.

We have covered several home server topics in great detail in the above posts. For some additional home server builds, I suggest the following posts:

Recommended HTPC / Home Server Builds:

2. Operating System

Once you have hardware figured out, the next big question is the operating system. In my opinion, Linux is the best operating system to build your home media server on. [Read: 10 Best Linux Home Server Distros โ€“ Stability, Performance, Ease of Use]

We have always recommended Ubuntu Server, more specifically the LTS (Long Term Support Releases), which are supported for 5 years. Once you build your server, you can let it run for 5 years with all security updates from the Ubuntu team.

Docker Home Server
Best Home Server Apps To Automate Media Management

Last year, I moved from Ubuntu Server to Linux Mint on Intel NUC Home server. Docker made the move from Sever Edition (Ubuntu) to Desktop Edition (Linux Mint) very easy.

All I had to do was install Linux Mint, install Docker and Docker Compose and start the stack. All my apps came right up. [Read: Moving from a Home Server to NAS (Synology) โ€“ The why, learnings, and tips]

We have tested this guide on both Ubuntu Server 18.04 LTS and Linux Mint 19.1. The next long-term release 20.04 Focal Fossa is just around the corner. We will update this guide shortly after it is released.

Having said the above, this Traefik v2 Docker Home Server guide can be followed on any operating system that can run Docker. This includes even Network Attached Storage devices such as Synology. We have successfully tested this guide on my Synology DS918+ with Docker and Docker Compose.

3. Domain Name

In my Traefik 1 guide, I discussed the topic of Dynamic DNS vs your own Domain Name.

With free Dynamic DNS services (DuckDNS, Afraid.org, etc.) you will have to go with the subdirectory URL structure (if you want to keep things convenient/simple). On the other hand, having your own domain name gives a lot more flexibility and privacy.

A private domain name only costs about $7.XX a year with Cloudflare and I strongly recommend that. For simplicity, we are writing this guide for private domain names.

If you have a free dynamic DNS with DuckDNS or Afraid.org, you will have to combine what is presented in this Docker Traefik guide with my previous guide on Traefik 1 to come up with your Docker Compose labels.

4. Proper DNS Records

In addition to having a private domain name, you will have to have correct DNS records with your domain registrar.

My DNS provider is Cloudflare, which is tested and verified to work with Traefik LetsEncrypt wildcard certificates. If your DNS provider is not listed as supported, then I recommend moving your DNS to Cloudflare, which is amazingly fast, and free. [Must Read: Cloudflare Settings for Traefik Docker: DDNS, CNAMEs, & Tweaks]

On Cloudflare, you have to point your root domain (example.com) to your WAN IP using an A record. Then, add either a wildcard CNAME (*.example.com) or individual subdomains, all pointing to your root domain (@ for the host), as shown below (this does not require a paid account).

Cloudflare Dns Entries For Traefik 2 Dns Challenge
Cloudflare Dns Entries For Traefik 2 Dns Challenge

Note that both records are "gray-clouded" at this point and won't be using Cloudflare's proxy features. Proxying, wildcard CNAME is not allowed in the free Cloudflare plan. To take full advantage of Cloudflare free plan, including the proxy feature (orange-cloud) that enhances security and performance, be sure to follow my Cloudflare tweaks for Traefik.

In addition to creating the DNS records, you will have to adjust Cloudflare's SSL settings to avoid indefinite redirects. Go to SSL/TLS settings for the domain and change SSL to Full as shown below.

Cloudflare &Quot;Full&Quot; Ssl For Traefik 2 Docker Setup
Cloudflare "Full" Ssl For Traefik 2 Docker Setup

Note: You may have to wait for a few minutes for the DNS entries to propagate. If you run Traefik before that, DNS challenge may fail and no SSL certificate will be generated. If you keep trying, Let's Encrypt may ban you temporarily for reaching the request limits. To counter this I propose using the LetsEncrypt staging server for initial validation and testing, as explained later in this docker traefik guide.

5. Port Forwarding for Traefik 2.0

Lastly, you need to enable port forwarding on your router or gateway.

Traefik Reverse Proxy uses ports 80 and 443. The traffic received on these ports from the internet must be forwarded to the internal/local IP address of the docker host running Traefik 2 service.

Docker Setup

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

The next big component is Docker. If you followed our previous Docker media server guide, you should have most of this topic covered. However, in the last two years since we published our guide, We have made several minor tweaks and improvements.

For most of the details, we will refer you back to our original post. We will highlight some of the key improvements and differences and here.

What is Docker?

We have already covered What is Docker and how it compares to a Virtual Machine such as VirtualBox. In addition, a brief overview of docker was presented in my previous guide.

Docker Vs Virtual Machines Made By Docker
Docker Vs Virtual Machines Made By Docker

Docker allows installing applications as containers that are isolated from the host operating systems. But the containers share system resources. Virtualization (eg. VirtualBox, VMware, etc.), on the other hand, runs isolated "full" systems, each one requiring its own maintenance.

Docker allows creating and destroying apps in seconds, without messing with the host system. The containers also boot in seconds and so your app is ready to roll very quickly.

Recommended Guides on Docker:

What is Docker Compose?

Compose is a tool for creating a list of all the containers (along with their configuration) that you want to run. It is written in YAML. Docker containers can be created from the commandline using various commands. Docker-compose makes it even easier.

Once you have your compose file ready, with a single command, you can create, start, or stop your apps at once.

Docker also makes applications more portable. When I moved from my previous Home Server to Intel NUC, I was up and running on my new server with over 50 apps in less in 15 minutes.

In this Docker Traefik 2 guide, we will be using Docker Compose to create our Docker Traefik home server.

Why use Docker to Setup a Home Server?

The traditional way of building a Home Server involves setting up the operating system, adding repositories, downloading the apps, installing the pre-requisites/dependencies, installing the app, and configuring the app. This is cumbersome and not portable.

We created AtoMiC ToolKit, which automates installation and maintenance of home server apps on Linux. But Docker makes things so easy that we have now abandoned AtoMiC ToolKit and moved on to Docker and Docker Compose.

Search For Containerized Apps On Docker Hub
Search For Containerized Apps On Docker Hub

In Docker, home server apps such as Sonarr, Radarr, Plex, etc. can be installed with ease without worrying about pre-requisites or incompatibilities. All requirements are already pre-packaged with each container images.

Most well-known apps are already containerized by the Docker community and available through the Docker Hub.

CAUTION: Make sure you use the container image from a reputable source for security reasons. Images with vulnerabilities can compromise your system. Review my Docker security best practices checklist for more information.

Install Docker and Docker Compose

Docker is available for Linux, Mac OS, and Windows 10. We have covered various Docker and Docker Compose installation procedures previously:

Here are some additional resources for various operating systems:

You must have Docker and Docker Compose running before moving forward with this Docker Traefik v2 guide.

Docker Configuration

Running docker commands on Linux requires sudo, in other words, elevation to admin privileges. You can get around this by adding the Linux user (yourself) to the Docker group as explained in my original Docker guide.

Some consider adding yourself to Docker group a security risk. This is true but the risk is mitigated significantly by paying attention and using trusted containers.

Create Docker Root Folder and Set Permissions

If you have been following my previous docker guides, then you should already have the following directory on Linux systems. This will be our docker root folder:

/home/USER/docker

USER is your username. If not, let's create this folder (which will house all your docker services data and .yml files) using the following command:

mkdir ~/docker

Next, let us setup appropriate permissions to the docker folder to avoid any permission error issues. Use the following commands in sequence:

sudo setfacl -Rdm g:docker:rwx ~/docker
sudo chmod -R 775 ~/docker

The above command forces any new sub-folders within the docker folder to inherit permissions from the docker folder. Some may disagree with the liberal permissions above but again this is for home use and it is restrictive enough.

Setting Up Environmental Variables for Docker and Docker Compose

We are going to put some frequently used information in a common location and call them up as needed using variable names. This is what setting up environmental variables means in simple terms.

In my previous, Docker and Traefik v1 tutorials, this information went into the /etc/environment file. This required logging out and logging in to take effect.

In this new Docker Traefik 2.0 guide, we are going to take a different route. All environmental variables are going to go into a .env file that will be located in the docker root folder created in the previous step.

Create the .env file (Note: the dot in front is not a typo) in the docker root folder (/home/USER/docker/.env) and add the following environmental variables to it:

PUID=1000
PGID=140
TZ="America/New_York"
USERDIR="/home/USER"
DOCKERDIR="/home/USER/docker"

Replace/Configure:

  1. PUID and PGID - the user ID of the linux user, who we want to run the home server apps as, and group ID of docker. Both of these can be obtained using the id command as shown below.

    User Id And Group Id
    User Id And Group Id

    In this guide, we are going to use 1000 for PUID, which is the user id of user and 140, which is the group id of docker group.

  2. TZ - the timezone that you want to set for your containers. Get your TZ from this timezone database.
  3. USERDIR - the path to the path to the home folder of the current user. You can also get this using the following command:
    cd ~ ; pwd
    
  4. DOCKERDIR - the docker folder that will house all persitent data folders for docker apps. We are going to call this the docker root folder.

These environmental variables will be referred to using $VARIABLE_NAME throughout the docker-compose file. Their values will be automatically pulled from the environment file that we created/edited above.

As we go through this Traefik 2 guide, we will continue to add more environmental variables to the .env file. You will find an example .env in my GitHub repo linked below.

That's it, the basic prep work to build our docker home server is done.

Basic Docker Commands to Know

Before we jump in, let's learn some basic commands to use through this Docker Traefik v2 guide. Most of the commands were discussed in detail in my previous docker server guide.

What we are going to present here is a simplified way to use these commands using bash_aliases on Ubuntu/Linux systems.

An example .bash_aliases file is provided in my GitHub repo.

Given below are some of the shortcut commands you can use to perform several docker and docker-compose related operations from commandline.

Aliases/Shortcuts for Docker Commands

  • dstopcont SERVICENAME - stop container
  • dstopall - stop all running containers
  • docdf - storage used by docker
  • docps - list of all containers

Aliases/Shortcuts for Docker Compose Commands

  • dcup2 - start Traefik 2 docker compose stack
  • dcdown2 - stop the entire Traefik 2 docker compose stack
  • dcrec2 SERVICENAME - recreate a specific service from Traefik 2 docker copose
  • dcrestart2 SERVICENAME - restart a specific container
  • dcpull2 - update all container images

Some of the aliases listed above are only for Traefik v2.0. There are a lot more shortcut commands (aliases), for including Traefik v1.0, than what is listed above. So check out the example bash_aliases file linked above.

This completes the docker setup and preparation part. Let's now move on to the Traefik 2.0 setup.

Traefik 2 Setup

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

Unlike my previous Traefik guide, we are not going to show you the docker-compose examples for all the 50 or so apps I run. Instead, my approach with this Traefik 2 guide is to show you how to set up Traefik 2 and a few apps as examples.

The example apps I have chosen will showcase various possibilities with Traefik 2 and cover some common scenarios. You can use these as examples and adapt the docker-compose examples for other apps.

Note: Both my GitHub Repo and this guide are created in such a way that both Traefik 1 and Traefik 2 can co-exist in the same docker root folder. But only one (Traefik 1 or 2) can be running at any time and proxy the services. For distinction, files/folders related to Traefik v1 one will have t1 and those related Traefik 2 will have t2 in their names.

Traefik Reverse Proxy Overview

Traefik reverse proxy provides convenience and security for your internet-facing services (eg. Radarr, Sonarr, SABnzbd, 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:

In a nutshell, we are going to use Traefik mainly to:

  • Put our apps behind a convenient and easy-to-remember URL
  • Add basic or Google OAuth 2.0 authentication for another layer of security 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 previous 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.

Traefik 1 vs Traefik 2

Traefik 2 introduced a lot of breaking changes. Many of the visitors who followed my previous guide woke up one morning and found that their setup was broken because their setup had automatically updated overnight to Traefik 2.

My Traefik 1 setup was working flawlessly. So why upgrade to Traefik 2? Let us spend some time to understand Traefik 2 before start setting up our Docker Traefik 2 home server.

Traefik v2.0 in Docker + containerd Updates

Migrating from Traefik v1 to v2 - Key Highlights

Traefik developers and the community has provided an excellent Traefik 1 to 2 migration guide. But here are some relevant highlights.

  1. Traefik version 2 has a different syntax and format for its configuration which means that the docker-compose.yml and traefik.toml used for Traefik 1.7.X cannot be used with the latest version of Traefik.
  2. You can choose the version with docker tags, using the available tags listed on DockerHub (and here: list of traefik releases with their tags). In order to stay up-to-date with the latest version, I recommend using the release tag for each version.
    • chevrotin = 2.2.X
    • cantal = 2.1.X
    • maroilles = 1.7.X
  3. Traefik v1.7 is no longer being developed, but will still receive any necessary security updates by using the tag maroilles. Throughout this guide we will be using the latest tag for Traefik v2.2 - chevrotin. Any future updates to version 2.2 of Traefik can then automatically be updated with a service like Watchtower or Ouroboros.
  4. Although Traefik version 2 requires a new configuration, Iโ€™ve found that it uses a lot of the same concepts as version 1.7. Here are some of the concepts that I feel have a better definition and purpose in v2:

    Along with these improved concepts comes a few new features, such as:

    • Routers
    • Middlewares
    • Services
    • TCP Routing

    If you do not understand any of these, do not worry. We will tell you what you need to know to get the Docker Traefik 2 server going.

Why Upgrade to Traefik 2?

My main reason for upgrading to Traefik 2 was for the improved header support, but I found that the thing I liked the most was how version 2 uses Middlewares.

This has dramatically simplified my configuration, and now itโ€™s extremely easy to set up new routes and services. You can also re-use the same configuration, which allows you to update multiple services by editing one config file.

I spent way too much time with v1 making sure each service was up-to-date with any improvements that I made.

We will go through all of this together and I hope you see the same great benefits that I have.

Traefik 2 Routers, Middlewares, and Services

Traefik v1 used two main concepts while managing requests - frontends and backends. Frontends would identify and modify how the request was handled, while backends would identify where to send the request. Traefik v2 no longer uses these definitions, and instead, three components need to be defined as part of our 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. In v1 the middlewares were defined on the frontend, but separating these features gives us additional flexibility and convenience. 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.

    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 the problem when a lot of things have changed at once. Start with a basic example and access the Traefik dashboard first, then continue to add services from there.
    • Traefik Dashboard/API Auth Required: The Traefik dashboard must be protected by authentication, otherwise you will need to use the insecure flag. By following this guide we will have a secure configuration and will not need to use this flag.
    • 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 Traefik 2 with Docker.

    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 to understand 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. The labels attached to each container in the docker-compose.yml, for example, are part of Traefikโ€™s dynamic configuration.

    You could also route to additional hosts as part of your dynamic configuration if you defined them with the File provider (which we arbitrarily called rules.toml in our Traefik v1 guide).

    Notice that we could define our dynamic configuration 1) with our Docker provider by using labels, and 2) with our File provider.

    Static Configuration

    While the 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 2. Only one can be used at the same time.

  1. Configuration file - can be in a TOML or YAML file (eg. traefik.toml or traefik.yaml).
  2. Command-line (CLI) arguments - these arguments are passed during docker run
  3. As environment variables - here is a list of all environmental variables.

These ways are evaluated in the order listed above.

In our Traefik v1 guide, we defined most of our static configuration using a traefik.toml file. The static config is read when Traefik starts, and cannot be changed during runtime.

In this guide, we are going to simplify things a bit by configuring Traefik version 2 without a traefik.toml file. Instead, we are going to take advantage of the CLI arguments method listed above. This gives us one less file to worry about and combines our Docker provider config into one docker-compose file.

Prep Work for Traefik 2

Next, letโ€™s do some prep work to get the Traefik v2 Docker container up and running. We are going to use the same folder structure that was used for our Docker media server guide.

Create Basic HTTP Authentication Credentials

While the focus of this post is to use Google OAuth 2, you have the option to go with Basic HTTP authentication if you wish. For this, we need to create a .htpasswd file with the login credentials.

We are going to put this file in $DOCKERDIR/shared folder. Use this HTPASSWD Generator, to create a username and password and add them to the $DOCKERDIR/shared/.htpasswd 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, $ signs do not need to be escaped.

Alternatively, you can use the following command on Linux to generate a username and password that is already escaped:

echo $(htpasswd -nb username mystrongpassword) | sed -e s/\$/\$\$/g

Save the file and exit.

Create Traefik 2 Environmental Variables

We already added several environmental variables for docker in the previous steps.

Note: In my GitHub repo (which should be your main source of reference for docker-compose examples as it has the most up-to-date information), I use several domain names: DOMAINNAME_HOME_SYNOLOGY (for my Docker Home Server on Synology), DOMAINNAME_CLOUD_SERVER (for my Dedicated Server in a Datacenter, with Proxmox), DOMAINNAME_SHB (domain name for this website), and DOMAINNAME_KHUB (domain name of another non-WordPress website I host). You may find any of these domain variables in my examples. Make sure to substitute this variable with your own.

Now open the same .env file and add the following new variables:

DOMAINNAME_CLOUD_SERVER=example.com
CLOUDFLARE_EMAIL=email@example.com
CLOUDFLARE_API_KEY=XXXXXXXXXXXX

Replace/Configure:

  1. example.com: Your private or dynamic DNS domain name.
  2. email@example.com: Email from your cloudflare account. This is only required only if you are doing DNS Challenge for Wildcard Traefik Letsencrypt certificates.
  3. XXXXXXXXXXXX: API key from your cloudflare account (Global API key from Profile page. Not the Zone ID or Account ID). Again, this only required for DNS Challenge for running apps under subdomains.
    Cloudflare Global Api Key For Dns Challenge
    Cloudflare Global Api Key For Let's Encrypt Dns Challenge

As explained before, in this guide we will be using DNS Challenge method to make Traefik get wildcard certificates from LetsEncrypt. To do that with Cloudflare we need the above two variables set (CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY).

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 environmental variables file.

Prepare Traefik 2 Folders and Files

Next, we need to create new folders for Traefik and ACME in the docker root folder ($DOCKERDIR) described previously:

mkdir traefik2
mkdir traefik2/acme

traefik2 is the folder we will use to store all Traefik 2.0 related configurations.

Docker cannot create missing files (only directories). So we will need to create an empty file for Traefik to store our LetsEnrypt certificate. So, from within the docker root folder, create acme.json empty file using the following command:

touch traefik2/acme/acme.json

Next, set proper permission for acme.json file using the following command:

chmod 600 traefik2/acme/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 a log file for Traefik to write logs to. From within docker root folder, let us create an empty log file:

touch traefik2/traefik.log

Create Traefik 2 Proxy Network

The last step is to create a network for Traefik 2 reverse proxy to use, using the following command:

docker network create t2_proxy

Alternatively, if you want to define your own subnet, you can use the following command:

docker network create --gateway 192.168.90.1 --subnet 192.168.90.0/24 t2_proxy

Customize 192.168.90.0/24 to suit your situation. The above command allows for the IP range 192.168.90.1 to 192.168.90.254 (254 IPs) for your docker services to use.

As shown later, you can set static IP addresses for your services if needed.

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

Traefik 2 Docker Compose

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

All basic information has been presented and the prep work has been done. Let's start with what you have all been waiting for. Building the Traefik 2 Docker Compose file.

Define Networks in Docker Compose

Create a file called docker-compose-t2.yml in the docker root folder we discussed earlier in this guide (same location as .env file, $DOCKERDIR). We will start out by adding the following to it:

Caution: Pay attention to blank spaces in the docker-compose examples below. Spacing is very important for YAML read your files correctly.
Update July 10, 2020: Traefik 2.2.2 introduced a breaking change. I have temporarily changed the chevrotin tag to 2.2.1. If you want to use the latest version either add --global.insecureSNI to the CLI arguments below or change rule=Host(`subdomain.example.com`) to rule=HostHeader(`subdomain.example.com`) in the traefik labels for services. I will update the guide and GitHub repo at some point.
version: "3.7"

########################### NETWORKS
networks:
  t2_proxy:
    external:
      name: t2_proxy
  default:
    driver: bridge

########################### SERVICES
services:
# All services / apps go below this line

Add Traefik 2 and Traefik 2 Dashboard

Hold on tight, this is going to be a long one. But don't worry we will do it in bits and pieces.

First, let's add the Traefik 2 service basics:

# Traefik 2 - Reverse Proxy
  traefik:
    container_name: traefik
    image: traefik:2.2.1 # the chevrotin tag refers to v2.2.x but introduced a breaking change in 2.2.2
    restart: unless-stopped

Nothing fancy here. Next, we will add our CLI arguments to configure Traefik 2.0. Most of the settings that used to go in traefik.toml in our Traefik 1 guide will go here instead.

    command: # CLI arguments
      - --global.checkNewVersion=true
      - --global.sendAnonymousUsage=true
      - --entryPoints.http.address=:80
      - --entryPoints.https.address=:443
        # Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/
      - --entrypoints.https.forwardedHeaders.trustedIPs=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/12,172.64.0.0/13,131.0.72.0/22
      - --entryPoints.traefik.address=:8080
      - --api=true
#      - --api.insecure=true
#      - --serversTransport.insecureSkipVerify=true
      - --log=true
      - --log.level=DEBUG # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      - --accessLog=true
      - --accessLog.filePath=/traefik.log
      - --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines
      - --accessLog.filters.statusCodes=400-499
      - --providers.docker=true
      - --providers.docker.endpoint=unix:///var/run/docker.sock
      - --providers.docker.defaultrule=Host(`{{ index .Labels "com.docker.compose.service" }}.$DOMAINNAME_CLOUD_SERVER`)
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=t2_proxy
      - --providers.docker.swarmMode=false
      - --providers.file.directory=/rules # Load dynamic configuration from one or more .toml or .yml files in a directory.
#      - --providers.file.filename=/path/to/file # Load dynamic configuration from a file.
      - --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.email=$CLOUDFLARE_EMAIL
      - --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare

The above Traefik v2.0 CLI arguments are what goes into a traefik.toml (or YAML) file if you decided to use one.

Here are some important lines that will require your attention:

--entrypoints.https.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. 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 basically 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).

--api.insecure=true

This line is commented out because we are going to put Traefik 2 dashboard behind authentication. If you, for whatever reason, do not want to enable authentication then uncomment this line by removing the # in front.

--serversTransport.insecureSkipVerify=true

I recommend leaving this line disabled (commented out), which will default to "false". Certain apps that require HTTPS for their web UI can be finicky behind Traefik (eg. NextCloud, Unifi Controller). If you set insecureSkipVerify to true this disables SSL certificate verification, so we want to avoid this if possible. We can use TCP routers (discussed later in the guide) that will allow us to access services like Nextcloud and Unifi Controller that require HTTPS.

--log.level=DEBUG

Start out by setting this to DEBUG. Once you ensure everything is working fine and LetsEncrypt Certificates are being pulled correctly, you can change this to WARN.

--providers.file.directory=/rules

As in our Traefik 1 guide, we are going to use a rules folder instead of a rules.toml file. In this folder, we will store some configurations that Traefik 2 can pick up 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.

--providers.file.filename=/path/to/file

This line is commented out because we are not going to use rules.toml. Instead, we are going to use a rules folder, which is enabled in the previous line of the configuration.

--certificatesResolvers.dns-cloudflare.acme Lines

Now we are going to talk about the last 5 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.email=$CLOUDFLARE_EMAIL
      - --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

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

Nothing to change with email or storage. For Cloudflare, make sure your environmental variable ($CLOUDFLARE_EMAIL) is set. Other providers may have different requirements and may not require an email.

provider: Change if you use a provider other than Cloudflare for DNS challenge.

Networks for Traefik 2 Dashboard

Next comes the network block. 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
#    networks:
#      - t2_proxy

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. If you want Docker to assign the container IP dynamically, comment out the first three lines and uncomment the last two.

Setting a static IP is helpful for some services like databases (MariaDB, InfluxDB, etc.) or whenever one of your containers needs to refer to another statically. For example, my PiHole can connect to Unbound using it's static Docker IP instead of exposing any ports to the host.

Security Options

The security options block allows us to set some additional security parameters. For example, using the block below, we can limit container processes from gaining additional privileges.

    security_opt:
      - no-new-privileges:true

In case you are interested, here are several more Docker security configurations.

Traefik 2 Ports

Next, we are going to specify the port information for Traefik 2. Traefik needs three ports: 80, 443, and 8080. The last one is needed for Traefik 2 Dashboard.

    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
      - target: 8080
        published: 8080
        protocol: tcp
        mode: host

As you can see, ports for Traefik 2 are the same as for Traefik 1.

Traefik 2 Volumes

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

    volumes:
      - $DOCKERDIR/traefik2/rules:/rules 
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $DOCKERDIR/traefik2/acme/acme.json:/acme.json 
      - $DOCKERDIR/traefik2/traefik.log:/traefik.log 
      - $DOCKERDIR/shared:/shared

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

As explained previously, I use the shared folder to share some common information between various services. Remember that we also placed the Basic HTTP authentication credentials in this folder.

The rest of the volumes/files can be used as-is.

Traefik Environmental Variables

We are going to pass two additional environmental variables for Traefik 2 service to use:

    environment:
      - CF_API_EMAIL=$CLOUDFLARE_EMAIL
      - CF_API_KEY=$CLOUDFLARE_API_KEY

Both $CLOUDFLARE_EMAIL and $CLOUDFLARE_API_KEY, which were set previously set using .env file will be passed on to CF_API_EMAIL and CF_API_KEY, respectively, for DNS challenge verification.

Traefik 2 Docker Labels

The last one is a big one: labels for Traefik 2. This is the part that has completely changed in docker-compose for Traefik 2, compared to Traefik 1.

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.

Next, we add the routers to redirect all HTTP traefik to the secure HTTPS port:

      # HTTP-to-HTTPS Redirect
      - "traefik.http.routers.http-catchall.entrypoints=http"
      - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"

Then, we add additional routers for entrypoints, certificate resolving, and domain information:

      # HTTP Routers
      - "traefik.http.routers.traefik-rtr.entrypoints=https"
      - "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.traefik-rtr.tls=true"
      - "traefik.http.routers.traefik-rtr.tls.certresolver=dns-cloudflare" # Comment out this line after first run of traefik to force the use of wildcard certs
      - "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME_CLOUD_SERVER"
      - "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME_CLOUD_SERVER"
#      - "traefik.http.routers.traefik-rtr.tls.domains[1].main=$SECONDDOMAINNAME" # Pulls main cert for second domain
#      - "traefik.http.routers.traefik-rtr.tls.domains[1].sans=*.$SECONDDOMAINNAME" # Pulls wildcard cert for second domain

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 HTTPS (secure entrypoint).

The rule is how we define which requests this router will apply to. The majority of my containers 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!

With tls, we are explicitly stating that this router should connect via TLS, and should not accept HTTP connections.

Take a note of the certresolver label. After initial testing and first Traefik run to pull LetsEncrypt wildcard certificates, we will have to comment out this line to force usage of wildcard certificates and stop creating separate certificates for individual services.

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 we defined in our CLI arguments.

You'll also notice I'm using different names for each router, for example, the HTTP-to-HTTPS Redirect router is arbitrarily named 'http-catchall' and the HTTP Router is 'traefik-rtr'. Routers are grouped using these names, but the name can be changed as you like to describe the router.

In the example above, we are using only one domain ($DOMAINNAME_CLOUD_SERVER and its wildcard *.$DOMAINNAME_CLOUD_SERVER). To define additional domains, uncomment the last two lines. Make sure $SECONDDOMAINNAME is set in your .env file. You can specify additional domains using domains[2], domains[3], etc. Traefik will pull SSL certificates for all of these domains.

The last part of the labels is a big one: middlewares. This will specify the security headers, set authentication, etc. First, let's start simple and then add additional labels.

If you do not want to set authentication for Treafik 2 dashboard (remember to enable --api.insecure=true) then all that is needed here is the following line to specify where the service to be proxied is available:

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

If you start your Traefik 2 service now, then we should be in business. However, remember that we are not going to make Traefik v2 dashboard accessible without authentication.

Traefik 2 Basic HTTP Authentication

First, let us set the simplest form of authentication and get things to work properly. This is where middlewares come into play.

Let's create our first middleware using our File provider. Create a file named middlewares.toml inside the Traefik 2 rules folder ($DOCKERDIR/traefik2/rules) and add the following content to it:

[http.middlewares]
  [http.middlewares.middlewares-basic-auth]
    [http.middlewares.middlewares-basic-auth.basicAuth]
#      username=user, password=mystrongpassword (listed below after hashing)
#      users = [
#        "user:$apr1$bvj3f2o0$/01DGlduxK4AqRsTwHnvc1",
#      ]
      realm = "Traefik2 Basic Auth"
      usersFile = "/shared/.htpasswd" #be sure to mount the volume through docker-compose.yml

The above code block adds a 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 .htpasswd file to store our credentials. So we will specify the path to usersFile.

Now let us add this middleware to our Traefik 2.0 service. This requires defining the middlewares we want to use on our router.

      ## 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 (@file) in the rules folder.

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

Note that the underlined part here middlewares-basic-auth@file matches the underlined name in [http.middlewares.middlewares-basic-auth] in the middlewares.toml file we created previously.

Testing Docker Traefik 2 Setup

So far, our docker-compose-t2.yml file should look something like this:

version: "3.7"

########################### NETWORKS
networks:
  t2_proxy:
    external:
      name: t2_proxy
  default:
    driver: bridge

########################### SERVICES
services:
# All services / apps go below this line

# Traefik 2 - Reverse Proxy
  traefik:
    container_name: traefik
    image: traefik:2.2.1 # the chevrotin tag refers to v2.2.x but introduced a breaking change in 2.2.2
    restart: unless-stopped
    command: # CLI arguments
      - --global.checkNewVersion=true
      - --global.sendAnonymousUsage=true
      - --entryPoints.http.address=:80
      - --entryPoints.https.address=:443
        # Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/
      - --entrypoints.https.forwardedHeaders.trustedIPs=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/12,172.64.0.0/13,131.0.72.0/22
      - --entryPoints.traefik.address=:8080
      - --api=true
#      - --api.insecure=true
#      - --serversTransport.insecureSkipVerify=true
      - --log=true
      - --log.level=DEBUG # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      - --accessLog=true
      - --accessLog.filePath=/traefik.log
      - --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines
      - --accessLog.filters.statusCodes=400-499
      - --providers.docker=true
      - --providers.docker.endpoint=unix:///var/run/docker.sock
      - --providers.docker.defaultrule=Host(`{{ index .Labels "com.docker.compose.service" }}.$DOMAINNAME_CLOUD_SERVER`)
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=t2_proxy
      - --providers.docker.swarmMode=false
      - --providers.file.directory=/rules # Load dynamic configuration from one or more .toml or .yml files in a directory.
#      - --providers.file.filename=/path/to/file # Load dynamic configuration from a file.
      - --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.email=$CLOUDFLARE_EMAIL
      - --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
    networks:
      t2_proxy:
        ipv4_address: 192.168.90.254 # You can specify a static IP
#    networks:
#      - t2_proxy
    security_opt:
      - no-new-privileges:true
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
      - target: 8080
        published: 8080
        protocol: tcp
        mode: host
    volumes:
      - $DOCKERDIR/traefik2/rules:/rules 
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $DOCKERDIR/traefik2/acme/acme.json:/acme.json 
      - $DOCKERDIR/traefik2/traefik.log:/traefik.log 
      - $DOCKERDIR/shared:/shared
    environment:
      - CF_API_EMAIL=$CLOUDFLARE_EMAIL
      - CF_API_KEY=$CLOUDFLARE_API_KEY
    labels:
      - "traefik.enable=true"
      # HTTP-to-HTTPS Redirect
      - "traefik.http.routers.http-catchall.entrypoints=http"
      - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # HTTP Routers
      - "traefik.http.routers.traefik-rtr.entrypoints=https"
      - "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.traefik-rtr.tls=true"
      - "traefik.http.routers.traefik-rtr.tls.certresolver=dns-cloudflare" # Comment out this line after first run of traefik to force the use of wildcard certs
      - "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME_CLOUD_SERVER"
      - "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME_CLOUD_SERVER"
#      - "traefik.http.routers.traefik-rtr.tls.domains[1].main=$SECONDDOMAINNAME" # Pulls main cert for second domain
#      - "traefik.http.routers.traefik-rtr.tls.domains[1].sans=*.$SECONDDOMAINNAME" # Pulls wildcard cert for second domain
      ## Services - API
      - "traefik.http.routers.traefik-rtr.service=api@internal"
      ## Middlewares
      - "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file" 

Note that "certresolver=dns-cloudflare" in the labels should match what is specified in "--certificatesResolvers.dns-cloudflare.acme...." in the Traefik 2 CLI arguments.

At this point, let's save the file and start Traefik 2. From the docker root folder, run:

docker-compose -f docker-compose-t2.yml up -d

Alternatively, you can use either dcup2 or dcrec2 traefik bash_aliases shortcuts to start Traefik 2.

Immediately, let's start following the logs for Traefik service to look for any obvious errors. Use the following command or the shortcut dclogs2 traefik. Again, from the docker root folder run:

docker logs -tf --tail="50" traefik

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, ignore them. Remember that we are using 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 LetsEncrypt certificate. We'll show you two of them here.

Let's open Traefik 2 dashboard in a browser and see the certificate information, as shown below. Notice the Fake LE Intermediate X1. This shows that an SSL (fake) certificate was obtained by Traefik 2 from LetsEncrypt staging server.

Traefik 2.0 Successful Staging Shows Fake Le Intermediate Certificate
Traefik 2.0 Successful Staging Shows Fake Le Intermediate Certificate

Alternatively, you can open acme.json file located inside the traefik2 folder ($DOCKERDIR/traefik2/acme/acme.json) 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.

Fetching Real LetsEncrypt Wildcard Certificates using Traefik 2

Since our staging was successful, let us now open the docker-compose-t2.yml file and comment out the following line (as shown below) so that we can reach the real LetsEncrypt server for DNS challenge.

#      - --certificatesResolvers.dns-cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory 

Save the Traefik v2 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 (remember to set the right permissions as described previously).

Next, recreate Traefik (dcrec2 traefik or the full command listed previously), and follow the logs (dclogs2 traefik or the full command listed previously) once again to make sure everything goes smoothly.

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. 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. Notice that the certificate includes both example.com and the wildcard *.example.com. 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 good so far and our Docker Traefik 2 stack is coming along quite well.

Forcing the Use of Wildcard Certs

Now that the wildcard certificates have been pulled, let us force Traefik to use that (*.example.com certificate) instead of creating a separate certificate for each service (service.example.com).

To do this, comment out the certresolver in the docker-compose file as shown below.

#      - "traefik.http.routers.traefik-rtr.tls.certresolver=dns-cloudflare"

Recreate your traefik 2.0 service and check to make sure the dashboard is accessible.

Securing Traefik 2 Dashboard

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

Now that everything is working great, let us start improving the security of our services. We already added some security with the basic authentication middleware.

Here are a few more you can add.

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

We are going to open the middlewares.toml file we created above and add the rate limit middleware just as we did for basic authentication. Add the following lines below what we already added for basic authentication (pay attention to the spacing/formatting).

  [http.middlewares.middlewares-rate-limit]
    [http.middlewares.middlewares-rate-limit.rateLimit]
      average = 100
      burst = 50

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:

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

In our Traefik 1 docker-compose, we had several browser security headers. Let us start adding those to our Traefik 2 services. You can add them as labels (like in our Traefik 1 docker-compose file) or as middleware using the File provider.

As you would see later in this guide, using the File provider for middlewares significantly reduces the size of docker-compose files by avoiding repetitions and reusing code.

Security Headers as Labels

Let us add the security header middlewares to what we already have in our labels. Notice that we will be creating a new name for this middleware traefik-headers simply by adding the label "traefik.http.middlewares....". Adding these labels to the Docker container will define the middleware, but you also need to add the middleware to the router, traefik-rtr (traefik.http.routers.traefik-rtr.middlewares=...).

In this middleware I donโ€™t have an โ€œ@โ€ included and only call the middleware as traefik-headers. By default, Traefik is assuming that this means traefik-headers@docker, which would be correct since we are defining the middleware in our "Docker provider" as labels, and not in our File provider.

      ## Middlewares
      - "traefik.http.routers.traefik-rtr.middlewares=traefik-headers,middlewares-rate-limit@file,middlewares-basic-auth@file" 
      - "traefik.http.middlewares.traefik-headers.headers.accesscontrolallowmethods=GET, OPTIONS, PUT"
      - "traefik.http.middlewares.traefik-headers.headers.accesscontrolalloworiginlist=https://$DOMAINNAME_CLOUD_SERVER"
      - "traefik.http.middlewares.traefik-headers.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.traefik-headers.headers.addvaryheader=true" 
      - "traefik.http.middlewares.traefik-headers.headers.allowedhosts=traefik.$DOMAINNAME_CLOUD_SERVER" 
      - "traefik.http.middlewares.traefik-headers.headers.hostsproxyheaders=X-Forwarded-Host"
      - "traefik.http.middlewares.traefik-headers.headers.sslredirect=true"
      - "traefik.http.middlewares.traefik-headers.headers.sslhost=traefik.$DOMAINNAME_CLOUD_SERVER" 
      - "traefik.http.middlewares.traefik-headers.headers.sslforcehost=true"
      - "traefik.http.middlewares.traefik-headers.headers.sslproxyheaders.X-Forwarded-Proto=https"
      - "traefik.http.middlewares.traefik-headers.headers.stsseconds=63072000"
      - "traefik.http.middlewares.traefik-headers.headers.stsincludesubdomains=true"
      - "traefik.http.middlewares.traefik-headers.headers.stspreload=true"
      - "traefik.http.middlewares.traefik-headers.headers.forcestsheader=true"
      - "traefik.http.middlewares.traefik-headers.headers.framedeny=true"
#      - "traefik.http.middlewares.traefik-headers.headers.customframeoptionsvalue=SAMEORIGIN" # This option overrides FrameDeny
      - "traefik.http.middlewares.traefik-headers.headers.contenttypenosniff=true"
      - "traefik.http.middlewares.traefik-headers.headers.browserxssfilter=true"
#      - "traefik.http.middlewares.traefik-headers.headers.contentsecuritypolicy=frame-ancestors 'none'; object-src 'none'; base-uri 'none';"
      - "traefik.http.middlewares.traefik-headers.headers.referrerpolicy=same-origin"
      - "traefik.http.middlewares.traefik-headers.headers.featurepolicy=camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';"
      - "traefik.http.middlewares.traefik-headers.headers.customresponseheaders.X-Robots-Tag=none,noarchive,nosnippet,notranslate,noimageindex,"

Please note that 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 for most services.

You can add the same set of security headers to all your other services/apps and modify them as you like. Be sure to change traefik-headers and traefik.$DOMAINNAME_CLOUD_SERVER" (allowedhosts and sslhost).

Security Headers as Middlewares File

As you can see there are a lot of options for security headers that we can add. With Traefik 1 we would have to copy these for each service, but Traefik 2 makes this really easy. Instead of using those labels, we can move them all to our middlewares file and call them into our docker-compose file as we did for rate limit and basic authentication.

Open middlewares.toml again and add the following lines below what we've already added (change example.com (3 instances)):

  [http.middlewares.middlewares-secure-headers]
    [http.middlewares.middlewares-secure-headers.headers]
      accessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
      accessControlMaxAge = 100
      hostsProxyHeaders = ["X-Forwarded-Host"]
      sslRedirect = true
      stsSeconds = 63072000
      stsIncludeSubdomains = true
      stsPreload = true
      forceSTSHeader = true
#      frameDeny = true #overwritten by customFrameOptionsValue
      customFrameOptionsValue = "allow-from https:example.com" #CSP takes care of this but may be needed for organizr. 
      contentTypeNosniff = true 
      browserXssFilter = true 
#      sslForceHost = true # add sslHost to all of the services
#      sslHost = "example.com"
      referrerPolicy = "same-origin" 
#      Setting contentSecurityPolicy is more secure but it can break things. Proper auth will reduce the risk.
#      the below line also breaks some apps due to 'none' - sonarr, radarr, etc.
#      contentSecurityPolicy = "frame-ancestors '*.example.com:*';object-src 'none';script-src 'none';"
      featurePolicy = "camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';" 
      [http.middlewares.middlewares-secure-headers.headers.customResponseHeaders]
        X-Robots-Tag = "none,noarchive,nosnippet,notranslate,noimageindex,"
        server = ""

While adding security headers via a middleware file simplifies the Traefik 2.0 docker-compose file, it does come with limitations compared to using labels. Notice that the lines sslForceHost and sslHost have been commented out.

These two options add a little bit more security but unfortunately will break apps. This is because the sslHost option requires a specific hostname (eg. service.example.com) and therefore one cannot provide a universal host that will apply to all services that will use Traefik 2.

Note: You cannot add only sslForceHost and sslForceHost as labels in docker-compose and leave the rest in the middleware file.

What is provided as labels will completely overwrite what is provided in the middlewares file. At this point, it looks like Traefik 2 does not append the two. So the only options are to either exclude those two lines (very slight decrease in security for convenience) or specify all security headers in the docker-compose files as labels (long docker-compose files). I chose to exclude (comment-out) those two lines in the middlewares.toml 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:

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

As always, recreate the services after any changes to the docker-compose file.

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.

Create a file called middleware-chains.toml in the rules folder and add the following lines:

[http.middlewares]
  [http.middlewares.chain-no-auth]
    [http.middlewares.chain-no-auth.chain]
      middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers"]

  [http.middlewares.chain-basic-auth]
    [http.middlewares.chain-basic-auth.chain]
      middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-basic-auth"]

What this code block does is that it creates two chains:

  1. chain-no-auth: This specifies the chain to use for services that we do not want to have an authentication layer in front (eg. Plex as it can interfere with Plex access on client devices). For these, we are only specifying the middlewares-rate-limit and middlewares-secure-headers middlewares.
  2. chain-basic-auth: For services that will use basic authentication in front of the service, we are specifying middlewares-basic-auth in addition to the other two.

With these two middleware chains defined, now we can modify the middlewares label in Traefik 2 docker compose as follows:

      - "traefik.http.routers.traefik-rtr.middlewares=chain-basic-auth@file" 

As evident, middleware chains further simplify the docker-compose files. You can call these chains on any container and they will use the same middleware configuration.

Google OAuth 2.0

May 11, 2020: I have now added Authelia as a private multifactor authentication system. [Read: Authelia Tutorial โ€“ Protect your Docker Traefik stack with Private MFA]

By using external middlewares and chains we can simplify our configuration, avoid errors, and also makes our config much easier to update. We can reuse our security headers to improve data transmission security, and basic auth has helped us to restrict access to our service.

There are two problems I have with basic auth though - 1) basic auth is only a single form of authentication, and 2) I have to sign-in each time in order to use my protected services. This can be pretty cumbersome after a while.

But there are other authentication systems that provide even more security (eg. 2-Factor Authentication) and convenience. Good examples are, Google OAuth 2.0, Authelia, and Keycloak.

In this article, we are going to add Google OAuth 2.0 for authenticating access to our services.

Note: If you do not need Google OAuth 2.0 authentication, you can skip this section and go to adding additional services.

By implementing Googleโ€™s OAuth 2.0 we can solve both of the concerns listed above. Implementing Googleโ€™s OAuth 2.0 is free and pretty straight-forward, with only a few small changes to adapt it for Traefik v2.

One thing to note about using Googleโ€™s OAuth service with your security headers. If the service is behind OAuth and youโ€™re trying to check whether your security headers are applied, you will probably receive a lower rating. This caused me a lot of concern at first, until realizing that the headers I was seeing were not actually for my service, but for Googleโ€™s.

Security Headers With Google Oauth
Security Headers With Google Oauth

Setting up the requirements for OAuth has already been discussed in our other post on Traefik Google OAuth 2.0. Please follow Steps 1 to 2 described there and come back to this post to continue.

Create OAuth Middleware and Chain

Let us now specify a middleware for OAuth. Open middlewares.toml file and add the following lines below what is already present:

  [http.middlewares.middlewares-oauth]
    [http.middlewares.middlewares-oauth.forwardAuth]
      address = "http://oauth:4181" # Make sure you have the OAuth service in docker-compose.yml
      trustForwardHeader = true
      authResponseHeaders = ["X-Forwarded-User"]

In addition, let us create a new middleware chain for services that will use Google OAuth. Open middleware-chains.toml and add the following lines below what is already present:

  [http.middlewares.chain-oauth]
    [http.middlewares.chain-oauth.chain]
      middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-oauth"]

Next, let us setup the OAuth Forwarder container.

Setting Up OAuth Forwarder Container

Open your docker-compose-t2.yml file and add the service for OAuth right below the traefik service we created previously.

# Google OAuth - Single Sign On using OAuth 2.0
  oauth:
    container_name: oauth
    image: thomseddon/traefik-forward-auth:latest
    restart: unless-stopped
    networks:
      - t2_proxy
    security_opt:
      - no-new-privileges:true
    environment:
      - CLIENT_ID=$GOOGLE_CLIENT_ID
      - CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
      - SECRET=$OAUTH_SECRET
      - COOKIE_DOMAIN=$DOMAINNAME_CLOUD_SERVER
      - INSECURE_COOKIE=false
      - AUTH_HOST=oauth.$DOMAINNAME_CLOUD_SERVER
      - URL_PATH=/_oauth
      - WHITELIST=$MY_EMAIL
      - LOG_LEVEL=info
      - LOG_FORMAT=text
      - LIFETIME=2592000 # 30 days
    labels:
      - "traefik.enable=true"
      ## HTTP Routers
      - "traefik.http.routers.oauth-rtr.entrypoints=https"
      - "traefik.http.routers.oauth-rtr.rule=Host(`oauth.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.oauth-rtr.tls=true"
      ## HTTP Services
      - "traefik.http.routers.oauth-rtr.service=oauth-svc"
      - "traefik.http.services.oauth-svc.loadbalancer.server.port=4181"
      ## Middlewares
      - "traefik.http.routers.oauth-rtr.middlewares=chain-oauth@file"
For Raspberry Pi: Use the arm tag for the image instead of latest. For example, image: thomseddon/traefik-forward-auth:2.1-arm.

Notice that we are using the OAuth middleware chain (chain-oauth) here for authentication instead of basic auth. Before starting the OAuth service, ensure that you have added the following environmental variables to your .env file:

  • GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET: Obtained by following our Traefik Google OAuth 2.0 guide.
  • OAUTH_SECRET: This is used to sign the cookie and should be random. Generate a random secret with:
    openssl rand -hex 16
    

    Alternatively, you may use an online service like this one, to generate your random secret.

    08 Random Oauth Secret | Smarthomebeginner
    Random Oauth Secret
  • MY_EMAIL: Google email id which will be used to authenticate.
  • URL_PATH: This is the same path as the Authorized redirect URI (https://oauth.example.com/_oauth) as explained in our Traefik Google OAuth 2.0 guide.

If you want more than one email id to be able to authenticate and reach your services, use different variables (eg. MY_EMAIL2, MY_EMAIL3, etc.) and list them all separated by commas for WHITELIST:

      - WHITELIST=$MY_EMAIL,$MY_EMAIL2,$MY_EMAIL3

You may also change the duration (LIFETIME) for which the authentication is valid from 30 days specified in seconds to another duration.

Note: If you need to logout, signout from your google services in any other tab/window and your OAuth for services will be invalidated.

Once done, use the docker-compose up command listed above or the shortcut dcup2 if you have bash_aliases setup. You should now be redirected to Google OAuth login page before reaching the service.

Google Oauth Login For Docker Services
Google Oauth Login For Docker Services

Reconfigure Traefik 2 Dashboard to use OAuth

At this point, our Traefik reverse proxy dashboard is setup to use basic authentication. Let us now change it to use Google OAuth. This is now as simple as changing chain-basic-auth to chain-oauth in the docker-compose file (shown below).

      - "traefik.http.routers.traefik-rtr.middlewares=chain-oauth@file" 

So there you go, Docker Traefik 2 setup with Google OAuth 2.

Once done, use the docker-compose up command listed above or the shortcut dcup2 if you have bash_aliases setup.

Adding Apps to Traefik 2 Docker Home Server Stack

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

Now that our Traefik 2 and OAuth are up and running, let us start adding some apps. Earlier, I listed examples of some of the apps that I wanted my Traefik 2 Docker server to run.

We are not going to show you how to install all of these apps using docker and put them behind Traefik 2 reverse proxy. Instead, we will show you a few apps that highlight a specific kind of configuration.

Once you read and understand this guide, it should be quite simple to use the docker-compose snippets from our GitHub Repos to set up the other apps that you are interested in (Anand's Repo and Seth's Repo).

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

Portainer with Traefik 2 and OAuth

We have covered Portainer installation previously. Portainer provides a WebUI to manage all your docker containers. I strongly recommend this for newbies. Here is the code to add (copy-paste) in the docker-compose file (pay attention to blank spaces at the beginning of each line):

# Portainer - WebUI for Containers
  portainer:
    container_name: portainer
    image: portainer/portainer:latest
    restart: unless-stopped
    command: -H unix:///var/run/docker.sock
    networks:
      - t2_proxy
    security_opt:
      - no-new-privileges:true
#    ports:
#      - "$PORTAINER_PORT:9000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $DOCKERDIR/portainer/data:/data 
    environment:
      - TZ=$TZ
    labels:
      - "traefik.enable=true"
      ## HTTP Routers
      - "traefik.http.routers.portainer-rtr.entrypoints=https"
      - "traefik.http.routers.portainer-rtr.rule=Host(`portainer.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.portainer-rtr.tls=true"
      ## Middlewares
#      - "traefik.http.routers.portainer-rtr.middlewares=chain-no-auth@file" # No Authentication
#      - "traefik.http.routers.portainer-rtr.middlewares=chain-basic-auth@file" # Basic Authentication
      - "traefik.http.routers.portainer-rtr.middlewares=chain-oauth@file" # Google OAuth 2.0
      ## HTTP Services
      - "traefik.http.routers.portainer-rtr.service=portainer-svc"
      - "traefik.http.services.portainer-svc.loadbalancer.server.port=9000"

Configure:

  1. PORTAINER_PORT: Port number on which you want the portainer WebUI to be available at. It could be the same port as the container: 9000 (must be free). Set PORTAINER_PORT in your .env file. Specifying ports is optional since we are using reverse proxy and can reach portainer at portainer.example.com.
  2. Authentication: Only OAuth middleware chain is enabled. If you want basic authentication or no authentication, uncomment the corresponding line and comment out the other middlewares.

Once done, use the docker-compose up command listed above or the shortcut dcup2 if you have bash_aliases setup.

Portainer WebUI should be available at https://portainer.example.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.

Organizr - Unified HTPC/Home Server Web Interface

A Docker home server with several apps may be cool but now you will have to remember all the different port numbers to access them. That is where Organizr comes in. Organizr provides a unified interface to access all your home server apps so you do not have to remember them individually.

In essence, Organizr is similar to HTPC Manager or Muximux.

Here is the code to add in the docker-compose file (pay attention to blank spaces at the beginning of each line):

# Organizr - Unified Frontend
  organizr:
    container_name: organizr
    image: organizrtools/organizr-v2:latest
    restart: unless-stopped
    networks:
      - t2_proxy
    security_opt:
      - no-new-privileges:true
#    ports:
#      - "$ORGANIZR_PORT:80"
    volumes:
      - $DOCKERDIR/organizr:/config
    environment:
      - PUID=$PUID
      - PGID=$PGID
      - TZ=$TZ
    labels:
      - "traefik.enable=true"
      ## HTTP Routers
      - "traefik.http.routers.organizr-rtr.entrypoints=https"
      - "traefik.http.routers.organizr-rtr.rule=Host(`$DOMAINNAME_CLOUD_SERVER`,`www.$DOMAINNAME_CLOUD_SERVER`)" 
      - "traefik.http.routers.organizr-rtr.tls=true"
      ## Middlewares
      - "traefik.http.routers.organizr-rtr.middlewares=chain-oauth@file" 
      ## HTTP Services
      - "traefik.http.routers.organizr-rtr.service=organizr-svc"
      - "traefik.http.services.organizr-svc.loadbalancer.server.port=80"

As explained for Portainer (above), enable and customize the ports and environment sections as needed. Add any missing environmental variables to .env. Save and update your stack (shortcut dcup2 with my bash_aliases example).

Why did I include Organizr as an Example?

Notice the following line:

      - "traefik.http.routers.organizr-rtr.rule=Host(`$DOMAINNAME_CLOUD_SERVER`,`www.$DOMAINNAME_CLOUD_SERVER`)" 

We are setting up Organizr to be served on my root domain (example.com or www.example.com). So that will be the face of my domain. From there, we have links to all my apps.

Note: I found that adding a lot of apps to Organizr slowed it down.

I only have 4 essential apps as tabs and a 5th tab for Heimdall. Heimdall is similar to Organizr and shows the links to rest of my apps. You can find Heimdall docker-compose snippet in my GitHub Repo.

Recommended Media Center Companion Apps:

MariaDB - MySQL Database

In my original Docker server guide, many people wanted MariaDB, which is one of the most commonly used database servers. In my case, Home Assistant (Hass.io), Guacamole, StatPing, and ZoneMinder are all writing data to MariaDB databases.

Here is the code to add in the docker-compose file (pay attention to blank spaces at the beginning of each line):

# MariaDB - MySQL Database
  mariadb:
    container_name: mariadb
    image: linuxserver/mariadb:latest
    restart: always
    networks:
      t2_proxy:
        ipv4_address: 192.168.90.250 
    security_opt:
      - no-new-privileges:true
    ports:
      - "3306:3306"
    volumes:
      - $DOCKERDIR/mariadb/data:/config
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    environment:
      - PUID=$PUID
      - PGID=$PGID
      - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD

As explained for Portainer (above), enable and customize the environment section as needed. Add any missing environmental variables to .env. Save and update your stack (shortcut dcup2 with my bash_aliases example).

Why did I include MariaDB as an Example?

Notice that we are specifying a static IP to MariaDB container (192.168.90.250), just as I did for Traefik 2 Dashboard.

In addition, we are mapping port 3306 of the container to port 3306 of the host. I can reach MariaDB through HOST-IP:3306 or 192.168.90.250:3306. Of course, this is redundant and I can disable the ports section (and only use 192.168.90.250:3306) or not assign a static IP to MariaDB container.

Guacamole - HTML 5 based Remote desktop, SSH, on Telnet Gateway

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 setup Guacamole as an app on host systems. But docker has made this very easy. In addition, OAuth provides much-needed security (since a security breach will expose all your remote connections).

Here is the code to add in the docker-compose file (pay attention to blank spaces at the beginning of each line):

# Guacamole - Remote desktop, SSH, on Telnet on any HTML5 Browser 
  guacamole:
    image: guacamole/guacamole:latest
    container_name: guacamole
    restart: unless-stopped
    networks:
      - t2_proxy
    security_opt:
      - no-new-privileges:true
#    ports:
#      - "$GUACAMOLE_PORT:8080"
    environment:
      GUACD_HOSTNAME: guacd
      MYSQL_HOSTNAME: $DB_HOST
      MYSQL_PORT: $DB_PORT
      MYSQL_DATABASE: guacamole
      MYSQL_USER: $GUAC_MYSQL_USER
      MYSQL_PASSWORD: $GUAC_MYSQL_PASSWORD
    labels:
      - "traefik.enable=true"
      ## HTTP Routers
      - "traefik.http.routers.guacamole-rtr.entrypoints=https"
      - "traefik.http.routers.guacamole-rtr.rule=Host(`guac.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.guacamole-rtr.tls=true"
      ## Middlewares
      - "traefik.http.routers.guacamole-rtr.middlewares=chain-oauth@file,add-guacamole" 
      - "traefik.http.middlewares.add-guacamole.addPrefix.prefix=/guacamole"
      ## HTTP Services
      - "traefik.http.routers.guacamole-rtr.service=guacamole-svc"
      - "traefik.http.services.guacamole-svc.loadbalancer.server.port=8080"

As explained for Portainer (above), enable and customize the ports and environment sections as needed. Add any missing environmental variables to .env. Save and update your stack (shortcut dcup2 with my bash_aliases example).

Note that Guacamole requires that a database be setup and initialized before it can work. Please refer to my Guacamole setup post for details.

Why did I include Guacamole as an Example?

Notice that we have a links section. Guacamole needs Guacamole Daemon (guacd) to be up and running as well. This runs as a separate container (see my GitHub repo for Docker Compose snippet). If guacd container is not up, Guacamole will not start or throw an error.

The main reason I include Guacamole as an example is to showcase the addPrefix.prefix middleware. If I visit guac.example.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.example.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 (described later). However, it does not work for all apps. For example, this does not work for ZoneMinder, which needs /zm/ prefix.

qBittorrent - Torrent downloader

qBittorrent is one of my favorite cross-platform BitTorrent clients. I use Transmission Bittorrent with VPN (e.g. Surfshark). But there is a reason I included qBittorrent as an example.

Here is the code to add in the docker-compose file (pay attention to blank spaces at the beginning of each line):

# qBittorrent - Torrent downloader
  qbittorrent:
    image: linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: always
    network_mode: container:transmission-vpn
    security_opt:
      - no-new-privileges:true
    volumes:
      - $DOCKERDIR/qbittorrent:/config
      - $USERDIR/Downloads:/downloads
    environment:
      PUID: $PUID
      PGID: $PGID
      TZ: $TZ
      UMASK_SET: 002
      WEBUI_PORT: 8168
    labels:
      - "traefik.enable=true"
      ## HTTP Routers
      - "traefik.http.routers.qbittorrent-rtr.entrypoints=https"
      - "traefik.http.routers.qbittorrent-rtr.rule=Host(`qbit.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.http.routers.qbittorrent-rtr.tls=true"
      ## Middlewares
      - "traefik.http.routers.qbittorrent-rtr.middlewares=chain-oauth@file"
      ## HTTP Services
      - "traefik.http.routers.qbittorrent-rtr.service=qbittorrent-svc"
      - "traefik.http.services.qbittorrent-svc.loadbalancer.server.port=8168"

As explained for Portainer (above), enable and customize the ports and environment sections as needed. Add any missing environmental variables to .env. Save and update your stack (shortcut dcup2 with my bash_aliases example).

Note that the default qBittorrent port is 8080. I use a different port (8168) and this is specified in my qBittorrent settings. Most likely you will have to change 8168 to 8080 (2 instances).

Why did I include qBittorrent as an Example?

As I said before, we have set up Transmission with Surfshark VPN. If VPN is not connected Transmission will stop. See my GitHub repo for Transmission with VPN Docker Compose snippet.

Surfshark VPN Exclusive Offer - 82% off ($2.39/month):
♦ Hide your browsing (no logs), Anonymize Streaming and Downloads
Wireguard Protocol support for VPN.
♦ Circumvent Geo/Country Restrictions and access worldwide content
♦ Works on Windows, Mac, Linux, Android, iOS, Router, and more
♦ 1 TB Encrypted Storage
♦ Money back guarantee - Sign Up Now

We are using the existing VPN connection on the transmission container to put qBittorrent also behind VPN by defining qBittorrent's network as below:

    network_mode: container:transmission-vpn

transmission-vpn is the name of my transmission container. With this method, you can put any container on the same network as another container. Note that if both containers expose the same ports, there could be conflicts and you may have to customize the ports. However, this is not a problem for Transmission and qBittorrent.

NextCloud โ€“ Your Own Cloud Storage

NextCloud (like OwnCloud) offers your own cloud storage to manage files and documents (like Google Drive/Docs).

You can find an example docker-compose for NextCloud on Seth's GitHub Repo.

Why did I include NextCloud as an Example?

If you choose to not enable the --serversTransport.insecureSkipVerify=true CLI argument (comment it out) you will need a slightly different setup. Nextcloudโ€™s WebUI is only accessible using an HTTPS port, and while Traefik communicates externally to clients using the LetsEncrypt cert, it communicates to services on the back-end using HTTP.

In the past, I needed to use the InsecureSkipVerify option, 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 which will allow us to "pass-through" our secure connection to Nextcloud. The labels that we will need to use are:

#### SEE GITHUB LINKED ABOVE FOR REST OF THE DOCKER-COMPOSE
      ## TCP Routers
      - "traefik.tcp.routers.nextcloud-tcp.entrypoints=https"
      - "traefik.tcp.routers.nextcloud-tcp.rule=HostSNI(`nextcloud.$DOMAINNAME_CLOUD_SERVER`)"
      - "traefik.tcp.routers.nextcloud-tcp.tls=true"
#      - "traefik.tcp.routers.nextcloud-tcp.tls.certresolver=dns-cloudflare"
      - "traefik.tcp.routers.nextcloud-tcp.tls.passthrough=true"
      ## TCP Services
      - "traefik.tcp.routers.nextcloud-tcp.service=nextcloud-tcp-svc"
      - "traefik.tcp.services.nextcloud-tcp-svc.loadbalancer.server.port=443"

Here you can see that we need to use the rule HostSNI on TCP routers to designate which incoming address Traefik should listen for.

The reason Iโ€™ve enabled TCP for only one of my services is that you cannot add middleware to TCP routes. This means, primarily, that you cannot add basic-auth or OAuth to these typically very sensitive interfaces.

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 50 apps, check our current GitHub Repos (Anand's Repo and Seth's Repo). These repos 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.

Adding non-docker or external apps behind Traefik

We have Traefik v2 working well for all of our services within our Docker network, but what about connecting to other hosts outside of Docker? We have a few of those (eg. Home Assistant, PiHole). 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 PiHole running on a Raspberry Pi. Let us create a file called app-pihole.toml in the rules folder and add the following content to it.

[http.routers]
  [http.routers.pihole-rtr]
      entryPoints = ["https"]
      rule = "Host(`pihole.example.com`)"
      service = "pihole-svc"
      middlewares = ["chain-oauth", "pihole-add-admin"]
      [http.routers.pihole-rtr.tls]
        certresolver = "dns-cloudflare"

[http.middlewares]
  [http.middlewares.pihole-add-admin.addPrefix]
    prefix = "/admin"

[http.services]
  [http.services.pihole-svc]
    [http.services.pihole-svc.loadBalancer]
      passHostHeader = true
      [[http.services.pihole-svc.loadBalancer.servers]]
        url = "http://192.168.1.26:80" # or whatever your external host's IP:port is

As you can see there are three sections: routers, middlewares, and services. PiHole frontend will be available at pihole.example.com. The service (backend) will be available at 192.168.1.26:80, which is the IP address of the Raspberry Pi that runs PiHole.

In addition, we also have a middleware pihole-add-admin. This middleware is the add-prefix middleware that I explained previously for Guacamole. It will automatically add the ending /admin to pihole.example.com and take us directly to the admin page, instead of the one below.

Pihole Without Addprefix Traefik Middleware
Pihole Without Addprefix Traefik Middleware

Since our rules directory is dynamic, simply by adding this file to that directory we have created the route. If youโ€™ve added an external host you should be able to connect to your service now, without restarting Traefik!

Troubleshooting Apps

Here are some issues either I ran into or others have mentioned.

Traefik 2 Not Pulling SSL Certificates

One of the most common reasons for this is that DNS changes have not propagated yet. Depending on the registrar, this can take several minutes.

If your staging was successful and fetching real certificates fail, wait a few more minutes and try again.

If after that Traefik 2 continues to fail to fetch certificates for some of the services, try adding the following label to each of the services.

      - "traefik.http.routers.jdownloader-rtr.tls.certresolver=dns-cloudflare"

Remember that we added this certresolver router to our Traefik 2 service and commented it out once wildcard certificates were pulled. We did not add this to any other service. Adding this seems to resolve the problem for some.

The downside to adding the certresolver router label to each of the services may be that Traefik reverse proxy will fetch separate certificates for each of the services instead of using the wildcard certificate.

Typos and Misnaming

Typos and misnaming are two of the most common mistakes people do. For example, when I was creating Portainer, I had a typo in a middleware name. This caused Portainer to not 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 middlewares-chain-no-auth (- instead of .). Once I fixed this issue, Portainer started right up.

Tools to Check Logs

Recently, one of my GitHub followers recommended Dozzle. I quickly became a big fan of it. Dozzle allows you to monitor logs of all your docker containers in real-time. This helps to troubleshoot and fix issues.

Following Logs With Dozzle
Following Logs With Dozzle

You can find the docker-compose example for Dozzle in my GitHub Repo.

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

Docker Traefik 2 Home Server - Final Thoughts

In this guide, I combined three of my previous (Docker media server, Traefik 1, and Google OAuth) guides into one. 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 their 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.

Traefik 2 may look drastically different from Traefik 1. But in reality, they are more similar than not. Hopefully, this guide broke things down enough for you to understand the concepts.

This post was co-authored by Seth, without whose help it would have been tough for me to migrate from Traefik 1 to 2. We are by no means "experts" on this topic. So if you have a better way of doing what we did, please feel free to share in the comments and we will respond and update the guide if needed.

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