What I wanted

Before actually doing anything, I needed to know exactly what I wanted. And it was pretty simple. I wanted a simple service running on an old laptop that would allow me to create blogposts about things I liked. I searched a bit on the internet, and found Hugo. It allows the creation of blog posts with markdown and a bit of yaml, which is perfect for me. I already had a domain name, and I’ve already used traefik in the past, so it was a no brainer for me to used this domain (dalmatheo.dev), and Traefik.

Setting up the basics

DNS records

The first thing to do when you want to host a reverse proxy (Traefik) is to set up the DNS records you want to use. Because I’m hosting my server at home, in a normal setup, when a client connect to a service (like the whoami traefik service), the IP of the client is not correct, because the traffic goes through my router that erase the original IP. Therefore, if I want a bit of protection (especially because I want to setup a nextcloud service later), I would want to find a way to get the real IP of the client. To do so, I can simply use cloudflare proxy feature that I have to enable in the DNS records.

The first record you want to create is an A record, taking all the traffic from your base domain (in my case dalmtheo.dev) and redirecting it to your home IP. You also have to enable the proxy from cloudflare for everything to work properly.

After that, just create another A record, taking all the traffic from wildcard (all subdomains of your base domain), redirecting it to your home IP. You also have to enable the proxy here. Here’s what it looks like at the end:

image showing DNS

Note that because we are using a reverse proxy, we will have to set under SSL/TLS the custom “Full (Strict) " option as shown here:

image Full (Strict)

Port Forwarding on Router

On your router, you will have to enable port forwarding for the 80 and 443 ports to the server that will host traefik.

Installing docker and docker-compose

We will be using docker-compose files to use traefik and our other services (Hugo in that case). I am using archlinux on my home server. To install docker on arch, you have to execute the commands:

sudo pacman -S docker docker-compose

You’ll then have to start the docker.service using systemctl:

sudo systemctl enable --now docker.service

Finally, you’ll have to add your user to the docker group to be able to execute all of the commands:

sudo usermod -aG docker $USER

If you need more support, you can check the dock here.

Setting up traefik

The docker compose file

Here’s a simple docker-compose.yml file that you can use:

services:
  traefik:
    image: traefik:latest
    environment:
        - CF_DNS_API_TOKEN=CloudflareToken
        - [email protected]
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_config:/etc/traefik
      - /etc/certs:/etc/certs
    command:
      # Dashboard et API
      - --api=true
      - --api.insecure=false

      # Providers
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.file.directory=/etc/traefik
      - --providers.file.watch=true

      # Web Entrypoint
      - --entrypoints.web.address=:80
      - --entrypoints.web.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/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22

      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.web.http.redirections.entrypoint.permanent=true
      - --entrypoints.web.transport.respondingTimeouts.idleTimeout=30s

      # Websecure Entrypoint
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.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/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.websecure.transport.respondingTimeouts.idleTimeout=30s

      # Let's Encrypt ACME DNS Challenge via Cloudflare
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,1.0.0.1:53
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=30s
      - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
      - --certificatesresolvers.letsencrypt.acme.storage=/etc/certs/acme.json
      - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory

      # Logs
      - --log.level=TRACE
      - --accesslog=true

    networks:
      traefik_bridge:
        ipv4_address: 172.20.0.2
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=dalmatheo:$$2y$$05$$CovVO0VpPnIFUEYTl9gSjeMKH1dvNEZX0AvIBw9hCqHMnQiz7y5/S"

  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    restart: unless-stopped
    networks:
      traefik_bridge:
        ipv4_address: 172.20.0.3
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

volumes:
  traefik_config:
    driver: local
  acme_data:
    external: true

networks:
  traefik_bridge:
    external: true
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Environment variables

    environment:
        - CF_DNS_API_TOKEN=CloudflareToken
        - ACME_EMAIL=Acme Email

Inside the file, you will have to complete this.

CF_DNS_API_TOKEN is the variable used by traefik to get the CF token to be able to access the DNS, and be able to modify it (or use dnschallenge for letsencrypt). To create one, you’ll have to go on your profile in the top right of your dashboard, then API Tokens, and you’ll have to set the Zone.Zone and Zone.DNS permissions.

ACME_EMAIL is the email that will be used to create your SSL/TLS certificate using letsencrypt. If your certificate is expired, it will send you an email, but you shouldn’t worry about that because traefik automatically renew them. However, this variable is still required.

Trusted IPs

Inside of the docker compose file, you’ll find two lines that looks like this:

      - --entrypoints.entrypoint.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/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22

Those lines are very important because it tells traefik to trust cloudflare IPs that will add headers for the reverse proxy to function correctly (it will basically tell traefik the real IP of the clients). You can learn more about it here.

Last modifications

To finalize this installation, the last thing you’ll have to do is to replace yourdomain.com with your actual domain name. At that point, traefik should be correctly configured. To check if everything works, you can execute inside of the folder that contains your docker compose file the following command:

docker-compose up

Traefik should get the certificate after around 1 minute. After that, you should be able to access whoami.yourdomain.com, and see your IP under X-Forwarded-For, as well as one of cloudflare ips.

Starting Traefik

To start traefik, you can simply use the following command where your docker compose file is:

docker-compose up

Additionnaly, you can use the -D argument to execute the container in the background.

Setting up Hugo

The docker compose file

Here’s a simple docker-compose.yml file that you can use:

services:
  hugo:
    image: ghcr.io/gohugoio/hugo:latest
    container_name: hugo
    restart: unless-stopped
    ports:
      - "1313:1313"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /hugopath:/src
    working_dir: /src
    command: server --bind=0.0.0.0 --baseURL https://yourdomain.com --appendPort=false
    networks:
      traefik_bridge:
        ipv4_address: 172.20.0.5
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.hugo.rule=Host(`yourdomain.com`)"
      - "traefik.http.routers.hugo.entrypoints=websecure"
      - "traefik.http.routers.hugo.tls.certresolver=letsencrypt"
      - "traefik.http.services.hugo.loadbalancer.server.port=1313"

networks:
  traefik_bridge:
    external: true
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Setting up the files for Hugo

For Hugo to work, you’ll have to create a folder (or a docker volume). I chose to create a folder, because it is easier to edit (by setting up a samba server for example). Inside of that folder (or volume), you’ll have to create a hugo.yaml or hugo.toml file. Here is a page from their docs that can help you a ton.

Changing the URL

In the docker compose file, you’ll find yourdomain.com written two times. You’ll have to replace that with your website domain, wether it is your base domain (yourdomain.com) or a subdomain (blog.yourdomain.com).

Starting Hugo

To start hugo, you can simply use the following command where your docker compose file is:

docker-compose up

Additionnaly, you can use the -D argument to execute the container in the background.