What you will learn today 🦦:

  • what is Traefik and why I decisively recommend to use it with Docker Swarm
  • how to show custom error page if Traefik returns 404 (service not found)

How I met Traefik 😁

Traefik is a top modern reverse proxy and load balancer. I started to use it from major version 1. At that moment, I already had Docker Swarm cluster, which I began to prepare for production use, and only one thing in the coherent picture has been left: “External Load Balancer”. Official docker doc had an example with HaProxy so it was obvious to try it out first. As I remember it took a significant amount of time to get HaProxy works smoothly with Swarm. But I wasn’t satisfied with the result:

  • static config which is needed to re-deploy in case of changes
  • *.cfg not supports variables substitution (out of the box)
  • no native support of Lets’s Encrypt

The production deadline was getting close, but it was out of my religion to leave such an ugly solution. I was searching the Internet and bumped into Traefik. Integration was surprisingly fast and easy. I thought to myself: “That is how modern DevOps should look like”. It is been 3 years since I deal with Traefik, and today I wanna tell about one of the most much wished-for feature.

Step by step setup 🐟 🐒 🦧 ⤴️ 👷🏼

error-midl
Traefik v2.x comes to us with middlewares and ErrorPage one of them.

Lett’s assume:

  • traefik dasboard domain: traefik-board.com
  • your app: my-app.com

0001. Add labels to traefik. Important here traefik.http.routers.traefik.middlewares=maintenance-page@docker. If you try to look for https://service-does-not-exists.com you will get friendly error page.

  traefik:
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.traefik.loadbalancer.server.port=8080"
        - "traefik.http.routers.traefik.rule=Host(`${traefik-board.com}`)"
        - "traefik.http.routers.traefik.entryPoints=websecure"
        - "traefik.http.routers.traefik.tls=true"
        - "traefik.http.routers.traefik.middlewares=maintenance-page@docker"

0010. Create dedicated service for error page. Make sure service would be as highly available as Traefik! I recommend to set deploy mode: global and placement: constraints: - node.role == manager. If you don’t, hello boring black page again.

maintenance-page:
   deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.maintenance-page.loadbalancer.server.port=80"
        - "traefik.http.routers.maintenance-page.rule=HostRegexp(`{host:.+}`)"
        - "traefik.http.routers.maintenance-page.priority=1"
        - "traefik.http.routers.maintenance-page.middlewares=maintenance-page@docker"
        - "traefik.http.routers.maintenance-page.entrypoints=websecure"
        - "traefik.http.routers.maintenance-page.tls=true"
        - "traefik.http.middlewares.maintenance-page.errors.status=400-599"
        - "traefik.http.middlewares.maintenance-page.errors.service=maintenance-page"
        - "traefik.http.middlewares.maintenance-page.errors.query=/"

These lines are essential. You tell the server to catch all requests and redirect them to maintenance-page service. priority=1 means “all-catcher” is not alone in the system. There is someone higher who wants to handle requests firstly, for example, “all-catcher” for http->https redirect.

traefik.http.routers.maintenance-page.priority=1
traefik.http.routers.maintenance-page.rule=HostRegexp(`{host:.+}`)

0011. Add priority=100 to app service.

  app:
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.frontend.rule=Host(`${my-app.com}`)"
        - "traefik.http.services.frontend.loadbalancer.server.port=80"
        - "traefik.http.routers.frontend.entrypoints=websecure"
        - "traefik.http.routers.frontend.tls=true"
        - "traefik.http.routers.frontend.priority=100"

Testing 🤹🏽‍♀️

Further reading 👩🏽‍💻

  • hot thread about this topic on the Containous Community Forum

So, that’s all for now 😊. 👩‍🚀 was happy to share my experience, and I’m sure this solution will save a bunch of time for someone. Happy hacking=) 🦾