Traefik + Docker-Compose Stack
I’ve been messing around with traefik and docker (using docker compose) for a while now, and I think it’s finally time for me to write out some steps on how to get it installed and working. In the past, I followed SmartHomeBeginner’s excellent guides, but the previous few times I’ve made some of my own changes, and it often helps to have a cumulative guide for others to follow. This will be frequently updated (probably) as I add more steps that I originally forgot, so feel free to leave a comment below.
Public IP and port forwards
You need to have a host with a public IP and ports 80 and 443 public. This can be checked with online tools such as YouGetSignal’s open port checker.
Installing stuff
First, we need to have an up to date server running ubuntu 20.04 or similar.
sudo apt update
sudo apt upgrade
Installing docker
Now, we need to install docker. A more in depth guide can be found here.
Some prerequisites:
sudo apt install apt-transport-https ca-certificates curl software-properties-common
Get docker repo gpg key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Add docker repo to apt:
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
Update apt lists:
sudo apt update
Install docker:
sudo apt install docker-ce
Check systemctl to ensure docker is running:
sudo systemctl status docker
Now, we need to add our username to the docker group.
Reboot the server (yes I know there are other ways to do this but rebooting is a simple approach) and then run this command:
sudo usermod -aG docker ${USER}
Reboot again (logging out and back in might also work, but rebooting is a simple approach). Then run
id
You should see the docker group now.
Installing docker compose
For a more in depth guide, go here. Note that this may not even be necessary as docker-compose functionality is being added to docker itself.
First, go to the docker compose release page (https://github.com/docker/compose/releases) and make note of the release number. As of now (July 2021) it is 1.29.2
Now, run this command, but before you hit enter, change the version number to the latest.
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Now, make it executable:
sudo chmod +x /usr/local/bin/docker-compose
To ensure that docker-compose is installed, run this:
docker-compose --version
You should see a build number. If not, something went wrong.
Setting Up Docker Folders
Now that we have docker and docker compose setup, we need to setup our directories and environmental variables.
THIS INFO HAS BEEN COPIED FROM ANOTHER GUIDE. PLEASE VISIT THE OTHER GUIDE!!! https://www.smarthomebeginner.com/traefik-2-docker-tutorial/
In your home directory, make the docker directory.
mkdir ~/docker
sudo setfacl -Rdm g:docker:rwx ~/docker
sudo chmod -R 775 ~/docker
Note, this command above might fail. In this case, install acl and retry.
sudo apt install acl
Now, within the docker directory, we will make our .env file with all of our environmental variables.
cd docker
nano .env
Add this to the .env file. You could use a command line tool like nano, or something like Visual Studio Code/Atom with a remote ssh plugin.
PUID=1000
PGID=997
TZ="America/New_York"
USERDIR="/home/USER"
DOCKERDIR="/home/USER/docker"
You will probably need to replace some information. See here: https://www.smarthomebeginner.com/traefik-2-docker-tutorial/
PUID is the user id of the server/computer. If you are using a free oracle server, this will be “ubuntu”. The PGID is the group ID of docker. You can find this information by running:
id
This might yield a result like below (the username replaced with USER)
uid=1000(USER) gid=1000(USER) groups=1000(USER),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd),997(docker)
In this case, the uid of USER is 1000, so PUID will be =1000. The group ID of docker is 997, so PGID will be =997
The USERDIR and DOCKERDIR also need to be changed, simply substitute USER for your username. This can also be found by running
cd ~ ; pwd
Domain Name Setup and Cloudflare
Before we go any further with the guide, I think it is probably time to get the domain and DNS setup. For many of my setups, I get a domain from freenom.com and cloudflare’s DNS service. Both are FREE. However, due to spam, cloudflare has limited using the API with a freenom TLD, including .tk, .ml and others. While you won’t be able to get a certificate from Let’s Encrypt via the automatic dnsChallenge method in Traefik, you can still run the container interactively and get a certificate. However, this process must be repeated every 90 days (expiration of the Let’s Encrypt Certificate). Another option is to just use cloudflare, and don’t even worry about a certificate. A self signed one can be used, and the cloudflare cert will be used (it is already proxying between the server and client anyway). However, this won’t work for non HTTPS services, like an email server.
This is where my guide finally starts to be different/unique. But it is convenient to have all the steps in one place. Regardless, please visit SmartHomeBeginner’s website and show some support/click on a few of the ads.
Getting a Free Domain Name
Head on over to freenom.com. Search for a domain name. Sometimes when you click on it, the domain will say unavailable, despite being available originally. If this happens, try searching for the exact domain name, including the TLD.
When checking out, make sure to change the period from 3 months to 12 months, so you can keep the domain for the longest time. One thing to keep in mind is to remember to cancel the domain name before that period ends, and then renew. Once, I forgot to cancel and renew, and when I went to get the domain again, Freenom was charging $10 for it. I had to wait a few months before I could get it for free again.
Agree to the terms and conditions, and then get the domain name.
Cloudflare
Now, we need to setup the DNS with Cloudflare. Go to cloudflare.com and create an account. Then click add a site on your dashboard. Type in the free domain name that you just got. If you do this immediately after you get the domain, it might fail. It should take you to a page asking what plan you want next. Just scroll to the bottom and select the free plan.
DNS records
You’ll need to add some DNS records now. First, add an A record. Under name, put “@”, and under IPv4 address, put your WAN IP. Leave it proxied.
Now, we need to add a wildcard record to allow for various subdomains. In the future, we can remove this and just make a CNAME record per service, but for now, a wildcard record will be simpler. Make a CNAME record, put “*” in the name field, and put “@” in the IPv4 address field. It should look like this
Now, click continue, and we have to change the nameservers. Copy the first nameserver that cloudflare gives you. Then, in Freenom, click services>my domains>manage domain on the recently acquired name. Then, under management tools, click nameservers. From here, click the “Use custom nameservers”. Paste the Cloudflare names ervers here and when finished, click “change nameservers”. Back in Cloudflare, click “Done, changed nameservers.”
Back to the Server
Now, we need to add a few more details to our .env file.
DOMAINNAME=example.com
[email protected]
CLOUDFLARE_API_KEY=XXXXXXXXXXXX
You can find instructions for these over at the SmartHomeBeginner guide here
Essentially, change DOMAIN to your domain from Freenom, CLOUDFLARE_EMAIL to your email for the Cloudflare account, and CLOUDFLARE_API_KEY to your global Cloudflare API key. Pretty self explanatory.
HTPASSWD
You will also probably want to create HTPASSWD credentials for basic auth. Go here for a generator. Now, create a new folder in the docker root directory:
mkdir shared
In here, make a new file called .htpasswd and fill it with the credentials that you just generated.
Traefik!
Make the Traefik folder.
mkdir traefik
mkdir traefik/acme
Now make the file containing our certificates and set permissions.
touch traefik/acme/acme.json
chmod 600 traefik/acme/acme.json
Create the Traefik log file
touch traefik/traefik.log
Create the traefik.yml configuration file.
touch traefik/traefik.yml
And fill it with this (change your email accordingly):
global:
checknewversion: "true"
sendanonymoususage: "true"
log:
level: ERROR
filepath: "/traefik.log"
api:
dashboard: true
insecure: false
# debug: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
# Allow these IPs to set the X-Forwarded-* headers - Cloudflare IPs: https://www.cloudflare.com/ips/
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"
traefik:
address: ":8080"
providers:
docker:
exposedByDefault: false
endpoint: "unix:///var/run/docker.sock"
network: traefik
defaultRule: "Host(`{{ index .Labels "com.docker.compose.service" }}.$DOMAINNAME`)"
swarmmode: false
file:
directory: "/rules"
# filename: "/etc/traefik/dynamic_config.yml"
watch: true
certificatesResolvers:
dns-cloudflare:
acme:
# caserver: "https://acme-staging-v02.api.letsencrypt.org/directory"
email: [email protected]
storage: "/acme.json"
dnsChallenge:
provider: manual
# delayBeforeCheck: 120
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
Now create the rules folder within the traefik folder:
mkdir traefik/rules
This is where we can configure middlewares for traefik. Within the rules folder, create a file called middlewares.toml and fill it with this (be sure to change example.com to your domain name):
[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
[http.middlewares.middlewares-rate-limit]
[http.middlewares.middlewares-rate-limit.rateLimit]
average = 100
burst = 50
[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 = ""
[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"]
Now, create another file in the rules directory called middleware-chains.toml and fill it with this:
[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"]
[http.middlewares.chain-oauth]
[http.middlewares.chain-oauth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-oauth"]
Create the traefik network. This is what will allow our containers to be networked in the reverse proxy.
docker network create --gateway 192.168.90.1 --subnet 192.168.90.0/24 traefik
In the docker root folder, we need to make our docker-compose file.
sudo nano docker-compose.yml
(again, you can use tools like VS Code or Atom, but nano also works)
Fill it with this content:
version: "3.7"
########## NETWORKS ##########
networks:
traefik:
external:
name: traefik
default:
driver: bridge
########## SERVICES ###########
services:
# This is where services go
Now, we will add our traefik snippet. Simply copy and paste and append it to the docker-compose file.
# Traefik 2 - Reverse Proxy
traefik:
container_name: traefik
image: traefik:latest # the chevrotin tag refers to v2.2.x but introduced a breaking change in 2.2.2
restart: unless-stopped
networks:
- traefik
security_opt:
- no-new-privileges:true
stdin_open: true #comment these out after first interactive run
tty: true #comment out after first interactive run
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/traefik/rules:/rules
- /var/run/docker.sock:/var/run/docker.sock:ro
- $DOCKERDIR/traefik/acme/acme.json:/acme.json
- $DOCKERDIR/traefik/traefik.log:/traefik.log
- $DOCKERDIR/traefik/traefik.yml:/traefik.yml
- $DOCKERDIR/shared:/shared
- $DOCKERDIR/certs:/certs
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`)"
- "traefik.http.routers.traefik-rtr.tls=true"
- "traefik.http.routers.traefik-rtr.tls.options=default"
# - "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"
- "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME"
# - "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=chain-basic-auth@file"
In the above snippet, traefik is configured so the user can interact with it. This is important if you are using a free domain name. On the first run, we will run the docker compose without disconnecting from it (using the -d flag) so we can add the proper dnschallenge records in cloudflare.
sudo docker-compose up