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 🐟 🐒 🦧 ⤴️ 👷🏼
 
 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 🤹🏽♀️
- case 1: app service goes down or updating replicas: 0:1-> see maitenance page
- case 2: user searched for https://service-does-not-exists.com -> see maitenance page
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=) 🦾