Karma Yoga and SSL Certificates

Or, How to Use Your Own Certificates with Træfik

Even though getting enlightened while doing the dishes sounds enticing, many people are nowadays equipped with a dishwasher. Luckily enough, technical innovation does not permeate all aspects of one’s life at the same pace, leaving leeway for inner work1. If you’re into IT and can relate, this blog post is for you. You don’t need to know anything about CI/CD, Kubernetes, or certbots.

Karma Yoga is the transformation of being that is based on one’s daily work in the world2. Let’s see how much karma we can create and get the witness, awareness, (and patience) in action3. We’ll use SSL certificates as an alibi and see how big of an endeavour we can make configuring Træfik with custom certificates4 5. We’ll expose 1 web app to a WAN via https with a database as backend.

I’ve been deploying containerized applications for many years now. Steps remain the same, growing on me like a mantra:

“भण्डारं क्लोन कुर्वन्तु, docker-compose.yaml इत्यस्य अनुकूलनं कुर्वन्तु, चित्रं निर्मायन्तु तथा च पात्रं चालयन्तु, भण्डारणं/दत्तांशकोशं विन्यस्यन्तु, पात्रं प्रति DNS अभिलेखबिन्दुं कुर्वन्तु, अन्त्यबिन्दुं सुरक्षितरूपेण WAN प्रति उजागरयन्तु, सम्भवतः VPN इत्यस्य पृष्ठतः. ॐ शान्ति: शान्ति: शान्ति:॥.”

This process takes me between 15 minutes and 0.5 days depending on the quality of the documentation and my level of caffeination. We are however not looking for speed or efficiency here, I’m just bragging.


Let’s start the exercise. Let’s first request the resources we will need to run our 2 containers. Durations are for indicative purposes only.

# Task Duration
1 Get compute (e.g., a Debian VM) with an IP on a WAN 1 day
2 Install Docker, build and run 2 containers 10 minutes
3 Get the domain name 4 months
4 Get the email configuration 6 weeks
5 Get SSL certificate 1 week
6 Get SSL certificate again, this time without a typo 20 days
6 Piece everything together 10 minutes
    Total: ~190 days

The net result is very yogic. After involving only 6 people and in less than 200 days one gets: 1 mostly idling VM (for 2 containers), no service redundancy, no backup strategy, no CI/CD, but another prospective round of emails in 1 year when certs will silently expire (you may want to put a reminder in your calendar now before you forget).


All the pieces are now here. Let’s add a third container in the mix. Træfik is our reverse proxy of choice. Below, docker-compose.yaml is given to serve the web app (myapp in the example) using a PostgreSQL database (db in the example). You’ll need to mount the certificate and key file you got via email into the Træfik container and reference them in traefik_dynamic.yaml, itself referenced in traefik.yaml. All code snippets are provided below.

docker-compose.yaml
version: "3"

services:

  # ------------- #
  # REVERSE PROXY #
  # ------------- #

  traefik:
    image: traefik:2.8.5
    container_name: traefik
    restart: always
    ports:
      - 80:80      # http
      - 443:443    # https
      - 8080:8080  # dashboard
    volumes:
      # System mounts
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/localtime:/etc/localtime:ro
      # Traefik config mounts
      - ./traefik.yaml:/etc/traefik/traefik.yaml:ro
      - ./traefik_dynamic.yaml:/etc/traefik/traefik_dynamic.yaml:ro
      - ./ssl/certs:/etc/ssl/certs
      # Traefik log mounts
      - /data/traefik/log:/var/log
    networks:
      - web
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.entrypoints=websecure"

  # ------- #
  # THE APP #
  # ------- #

  myapp:
    restart: always
    hostname: myapp
    container_name: myapp-app
    depends_on:
      - db
    image: myapp:1
    volumes:
      - /data/app/files:/usr/src/files:rw
    labels:
      # Enable Træfik
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.service=myapp"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.services.myapp.loadbalancer.server.port=3000"
      - "traefik.http.routers.myapp.rule=Host(`myapp.com`, `www.myapp.com`)"
      # HTTP redirect
      - "traefik.http.middlewares.https_redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.https_redirect.redirectscheme.permanent=true"
      - "traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)"
      - "traefik.http.routers.http_catchall.entrypoints=web"
      - "traefik.http.routers.http_catchall.middlewares=https_redirect"
    env_file: .env-local
    networks:
      - web
      - myapp

  # ------ #
  # THE DB #
  # ------ #

  db:
    restart: always
    container_name: myapp-db
    image: postgresql:15
    volumes:
      - /data/db/pgdata:/var/lib/postgresql/data:rw
    env_file: .env-local-postgres
    networks:
      - myapp

networks:
  myapp:
  web:
    external: true
traefik.yaml
# REFERENCE: https://github.com/traefik/traefik/blob/master/traefik.sample.yml

global:
  checkNewVersion: true
  sendAnonymousUsage: false

entryPoints:
  web:
    address: :80

  websecure:
    address: :443
    http:
      tls: {}

log:
  level: INFO
  filePath: /var/log/traefik.json
  format: json

accessLog:
  filePath: /var/log/traefik-accesslog.json
  format: json

api:
  insecure: true  # False when prod
  dashboard: true # False when prod

ping:
  entryPoint: traefik

providers:
  docker:
    endpoint: unix:///var/run/docker.sock
    watch: true
    network: web
    exposedByDefault: false
  file:
    filename: "/etc/traefik/traefik_dynamic.yaml"
    watch: true
traefik_dynamic.yaml
tls:
  certificates:
    - certFile: /etc/ssl/certs/certificate.pem
      keyFile:  /etc/ssl/certs/private.key

After issuing ~$ docker compose up, you should be able to read Træfik’s logs. It should look like traefik.json provided below.

traefik.json
{"level":"info","msg":"Traefik version 2.8.5 built on 2022-09-13T15:19:09Z","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n","time":"2022-09-22T17:57:17+02:00"}
{"level":"warning","msg":"Traefik Pilot is deprecated and will be removed soon. Please check our Blog for migration instructions later this year.","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"Starting provider aggregator aggregator.ProviderAggregator","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"Starting provider *file.Provider","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"Starting provider *traefik.Provider","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"Starting provider *acme.ChallengeTLSALPN","time":"2022-09-22T17:57:17+02:00"}
{"level":"info","msg":"Starting provider *docker.Provider","time":"2022-09-22T17:57:17+02:00"}

Here are some things you may want to check if stuff goes wrong:

  1. Are you using the right certificate and key file?
    • Use the file CLI tool to check the types of your files on the file system. You should see server.pem: PEM certificate, private.key: ASCII text
    • You can cat the content of the files:
    • server.pem is contained between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----
    • private.key is contained between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----
  2. Have a look into Træfik’s logs: /var/log/traefik.json, not just the stdout provided by Docker.
  3. Map port 8080 in docker-compose.yaml and allow api insecure and dashboard in traefik.yaml to peak into the services status.

Namaste 🔮

Categories:  #web  #spirituality