Traefik setup

Written on

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 :

 

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