Traefik setup
Original article link. I love that article, but I find it really time consuming to refer it each time, hence this post.
I prefer everyone to read the source article at least once before diving into this article (or continue if you have some idea about Traefik)
1. Docker installation and config:
sudo apt-get install docker-ce
Verify with
sudo docker run hello-world
Find the latest version here - https://github.com/docker/compose/releases and install docker compose with this command
sudo curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo usermod -aG docker ${USER}
We are adding current user to docker group, because we don't want to sudo every docker command
Let's also create a network called skynet with the following command
docker network create --gateway 192.168.90.1 --subnet 192.168.90.0/24 skynet
2. Adding environment variables
Add the following environment variables to /etc/environment
PUID=1000
PGID=1000
TZ="Asia/Kolkata"
USERDIR="/home/USER"
DOCKERDIR="/home/USER/docker"
MYSQL_ROOT_PASSWORD="passsword"
DOMAINNAME=DOMAIN
NAMECHEAP_API_USER=USERNAME_OR_EMAIL_AS_PER_DOCUMENTATION
NAMECHEAP_API_KEY=XXXXXXXXXXXX
Find your PUID and PGID by typing id
Also set your UserDir and Timezone
Replace NAMECHEAP_API details with your domain provider's details, find those here https://doc.traefik.io/traefik/v1.7/configuration/acme/#wildcard-domains
Also note your domain provider's Provider code
from the table, we will need it in step 5.
Namecheap had an additional step to whitelist the IP from where I will be making the calls, read the documentation of your document provider properly
You will need to logout and log back in for the environmental variables to take effect or open a new shell
3. Creating a .htpasswd:
Create a file in ~/docker/shared
Example content:
admin:{SHA}joB75e5asdfasdfasdfvv6KKOFc=
Note that the password must be hashed, you can use this link to hash it https://hostingcanada.org/htpasswd-generator/
4. Preparing traefik stuff
Create two directories ~/docker/traefik
and ~/docker/traefik/acme
Crate a file acme.json and set permission of that file to 600 with chmod 600 ~/docker/traefik/acme/acme.json
One more file for log file ~/docker/traefik/traefik.log
5. Creating docker compose for traefik
This is the hard part.
Let's start by creating the traefik.toml file in ~/docker/traefik/docker-compose.yml with the following content:
version: "3.7"
########################### NETWORKS
networks:
skynet:
external:
name: skynet
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
- --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
# - #IMPORTANT: Change the '@' to '{' in the next line, had to change because it was messing with my template engine
- --providers.docker.defaultrule=Host(`@@ index .Labels "com.docker.compose.service" }}.$DOMAINNAME`)
- --providers.docker.exposedByDefault=false
- --providers.docker.network=skynet
- --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-namecheap.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt Staging Server - uncomment when testing
- --certificatesResolvers.dns-namecheap.acme.user=$NAMECHEAP_API_USER
- --certificatesResolvers.dns-namecheap.acme.storage=/acme.json
- --certificatesResolvers.dns-namecheap.acme.dnsChallenge.provider=namecheap
- --certificatesResolvers.dns-namecheap.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
networks:
skynet:
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/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/shared:/shared
environment:
- NAMECHEAP_API_USER=$NAMECHEAP_API_USER #based on your domain provider
- NAMECHEAP_API_KEY=$NAMECHEAP_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`)"
- "traefik.http.routers.traefik-rtr.tls=true"
- "traefik.http.routers.traefik-rtr.tls.certresolver=dns-namecheap" # 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=middlewares-basic-auth@file"
Change the namecheap part to your domain provider details, everything else is mostly self explanatory.
Couple of things to note are :
- We are going to use a rules folder instead of
rules.toml
. - Usually what comes before the equal to sign are names, and can be anything, in this example those are
http-catchall
,traefik-rtr
,dns-namecheap
6. Middlewares
Let's create the middleware file we mentioned in the configuration
Create middlewares.toml in ~/docker/traefik/rules
with the following content
[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
This adds a basic authentication middleware
7. Let's try starting it up
docker-compose -f docker-compose.yml up -d
Check logs with
docker logs -tf --tail="50" traefik
If something goes wrong stop with following command and start it again
docker-compose down
This might be a good time for you to add some aliases that could help you.
Note: You need to have a A record
pointing to *
in your domain provider
After the first successful execution(verify with logs or you can confirm by going to traefik.yourdomain.com and clicking on the ceritificate), comment line 44, so we are not using Let's Encrypt's staging server.
Delete the contents of acme.json
(If you delete the file, remember to assign 600 permission).
Now fire up the docker again, and you should've gotten valid https certificates.
Also comment line #89 after your first successful execution with the non-staging server
8. Example application
You're essentially done, let's try to see how we can onboard applications.
For demo purpose I'm going to try to onboard Heimdall application.
Create a new folder ~/docker/heimdall
and create a docker-compose.yml
will content similar to the following
version: "2.1"
services:
heimdall:
image: ghcr.io/linuxserver/heimdall
container_name: heimdall
restart: always
networks:
- skynet
security_opt:
- no-new-privileges:true
volumes:
- $DOCKERDIR/heimdall/config:/config
environment:
PUID: $PUID
PGID: $PGID
TZ: $TZ
labels:
- "traefik.enable=true"
## HTTP Routers
- "traefik.http.routers.heimdall-rtr.entrypoints=https"
- "traefik.http.routers.heimdall-rtr.rule=Host(`apps.$DOMAINNAME`)"
- "traefik.http.routers.heimdall-rtr.tls=true"
## Middlewares
- "traefik.http.routers.heimdall-rtr.middlewares=middlewares-basic-auth@file"
## HTTP Services
- "traefik.http.routers.heimdall-rtr.service=heimdall-svc"
- "traefik.http.services.heimdall-svc.loadbalancer.server.port=80"
networks:
skynet:
external:
name: skynet
Fire the container with docker-compose up -d
and navigate to apps.yourdomainname.com, it should be up.
Have fun adding different apps!
UPDATE:
Updated middlewares.toml file
[http.middlewares]
[http.middlewares.middlewares-basic-auth]
[http.middlewares.middlewares-basic-auth.basicAuth]
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:redsnow.in" #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 = "yourdomain.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 '*.redsnow.in:*';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 = ""
Also create a new file called middlewares-chains.toml with the following content
[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"]
After that in the middlewares of your docker-compose.yml line add the lines according to the needs like
- "traefik.http.routers.blog_static-rtr.middlewares=chain-no-auth@file"
or
- "traefik.http.routers.yt-rtr.middlewares=middlewares-basic-auth@file"
Have fun!
Tags · Tech, Blog