Gradual Migration from Traefik 1.x to 2.x
Published: 2020-07-19 02:48:33

---
canonical_url: https://containo.us/blog/gradual-migration-from-traefik-1-to-2/
description: In this post, I share a migration strategy that helped me move to Traefik 2 with very little downtime, one service at a time, with an easy way to rollback.
---

*This post first appeared at Countainous' blog at https://containo.us/blog/gradual-migration-from-traefik-1-to-2/*

Are you a happy Traefik user? Join the club! I use Traefik as a reverse proxy to manage the ingress of several dozen services in a Docker Swarm cluster, and couldn't be happier with it. Since its introduction in early 2015, Traefik has grown in maturity and popularity (don't take my word, look at [the project's stargazers over time][1]. When Traefik v2 was released I couldn't help but think about migrating, but I had one major concern: downtime.

Traefik's documentation explains how to migrate configurations from 1.x format to 2.x format however, as in any system with some degree of complexity, migrating is not just about changing configurations but managing them. You have to make sure everything keeps running smoothly and be prepared to rollback in case something goes wrong -have you heard of Murphy's Law? Also, you probably don't want to migrate the whole system at a time, or you could quickly find yourself trying to put out more fires than you can handle.

In this post, I will share a migration strategy that helped me move to Traefik 2 with very little downtime, one service at a time, with an easy way to rollback. For the sake of clarity and brevity, I will start from a single Traefik instance with two backend services and will keep everything in a single Docker Swarm stack. The same strategy could be used in a clustered Traefik deployment with many more backend services as well. In fact, this scenario is where Traefik shines the brightest.

## Initial setup

Let's start from the following setup, with a Traefik 1 instance as a reverse proxy and two Nginx services, all running on Docker Swarm:

<figure>
![Initial setup. Traefik 1 handling all routing](https://dev-to-uploads.s3.amazonaws.com/i/194s0f4mgvcqenai6dro.png)
<figcaption>Initial setup. Traefik 1 handling all routing</figcaption>
</figure>

This configuration can be deployed to the swarm with the following stack definition:

```yaml
# docker-compose.yaml

# Version >= 3.3 so configs are available
version: "3.4"

networks:
  traefik-public:
    external: true

configs:
  index1:
    # Contains string "1"
    file: ./index1.html
  index2:
    # Contains string "2"
    file: ./index2.html

services:
  traefik1:
    image: traefik:v1.7
    ports:
      - "80:80"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
    command: >
      --docker
      --docker.swarmmode
      --entrypoints='Name:http Address::80'
    networks:
      - traefik-public

  web1:
    image: nginx:1-alpine
    deploy:
      labels:
        - traefik.enable=true
        - traefik.frontend.rule=Host:web1.docker.local
        - traefik.port=80
        - traefik.webservice.frontend.entryPoints=http
    configs:
      - source: index1
        target: /usr/share/nginx/html/index.html
    networks:
      - traefik-public

  web2:
    image: nginx:1-alpine
    deploy:
      labels:
        - traefik.enable=true
        - traefik.frontend.rule=Host:web2.docker.local
        - traefik.port=80
        - traefik.webservice.frontend.entryPoints=http
    configs:
      - source: index2
        target: /usr/share/nginx/html/index.html
    networks:
      - traefik-public
```

Files `index1.html` and `index1.html` contain strings `1` and `2` respectively:

```bash
echo 1 > index1.html
echo 2 > index2.html
```

With the above configuration, you can now create a Docker Swarm (if you don't already have one), an overlay network for Traefik and deploy the stack:

```bash
docker swarm init
docker network create --driver=overlay traefik-public
docker stack deploy -c docker-compose.yaml traefik
```

When the stack deployment finishes you will be able to query the defined Nginx services as `web1.docker.local` and `web2.docker.local`. In the example below I’m using curl:

```bash
curl -H Host:web1.docker.local http://127.0.0.1
# 1
curl -H Host:web1.docker.local http://127.0.0.1
# 2
```

It may take a few seconds to start the containers so if you get a `404 page not found` response just wait and try again.

## Traefik 2 with fallback to Traefik 1

You now have a working Traefik 1.x reverse proxy and two backend services. Let's migrate it to 2.x! Next you are going to add a Traefik 2 service which will run alongside and proxy requests to the existing one. Incoming requests will be routed to the Traefik 2 service and if no routes are matched they will then be routed to the Traefik 1 service.

<figure>
![Traefik 2 routing all requests to Traefik 1](https://dev-to-uploads.s3.amazonaws.com/i/xhhpqq469k6jz88x1hc7.png)
<figcaption>Traefik 2 routing all requests to Traefik 1</figcaption>
</figure>

Deploy these changes to the stack definition file:

```diff
 # docker-compose.yaml

 ...
 configs:
   ...
+  # Dynamic configuration for Traefik 2 (see below)
+  traefik2-providers:
+    file: ./traefik2-providers.yaml

 services:
   traefik1:
     image: traefik:v1.7
-    ports:
-      - "80:80"
   ...
+  traefik2:
+    image: traefik:v2.1
+    ports:
+      # The HTTP port
+      - "80:80"
+    volumes:
+      # So that Traefik can listen to the Docker events
+      - /var/run/docker.sock:/var/run/docker.sock
+    command: >
+      --providers.docker
+      --providers.docker.swarmMode
+      --providers.file.directory=/etc/traefik
+      --providers.file.filename=providers.yaml
+      --entryPoints.http.address=:80
+      --api.insecure
+    configs:
+      - source: traefik2-providers
+        target: /etc/traefik/providers.yaml
+    networks:
+      - traefik-public
```

The `traefik2-providers.yaml` file used in the `traefik2-providers` config directive for the `traefik2` service defines a catch-all route that forwards unmatched requests to the `traefik1` service:

```yaml
# traefik2-providers.yaml

http:
  routers:
    # Define a catch-all router that forwards requests to legacy Traefik
    to-traefik1:
      # Catch all domains (regex matches all strings)
      # See https://github.com/google/re2/wiki/Syntax
      rule: "HostRegexp(`{domain:.+}`)"
      # If the rule matches, forward to the traefik1 service (see below)
      service: traefik1
      # Set the lowest priority, so this route is only used as a last resort
      priority: 1

  services:
    # Define how to reach legacy Traefik
    traefik1:
      loadBalancer:
        servers:
          # Legacy Traefik is part of the same stack so,
          # hostname defaults to service name
          - url: http://traefik1
```

Redeploy the stack and check everything is still working as expected:

```bash
docker stack deploy -c docker-compose.yaml traefik
# ...
curl -H Host:web1.docker.local http://127.0.0.1
# 1
curl -H Host:web2.docker.local http://127.0.0.1
# 2
```

## Traefik 2 replacing Traefik 1

Next let's set up Traefik 2 to handle requests to `web1`, as in Image 3:

<figure>
![Traefik 2 handling web1 service's routing](https://dev-to-uploads.s3.amazonaws.com/i/w4y36sorphcje0v8u8qe.png)
<figcaption>Traefik 2 handling web1 service's routing</figcaption>
</figure>

This setup can be achieved by updating `web1` service labels to match Traefik 2 format as follows:

```yaml
...
services:
  ...
  web1:
    ...
    deploy:
      labels:
        - traefik.enable=true
        - traefik.http.routers.web1.rule=Host(`web1.docker.local`)
        - traefik.http.services.web1.loadbalancer.server.port=80
    ...
```

Redeploy the stack and again check everything is still working as expected:

```bash
docker stack deploy -c docker-compose.yaml traefik
# ...
curl -H Host:web1.docker.local http://127.0.0.1
# 1
curl -H Host:web2.docker.local http://127.0.0.1
# 2
```

Now repeat the process for `web2` service. If something goes wrong, you just need to revert to a previous working configuration for the affected service, redeploy, and start over. In a real-world scenario with lots of services, migration can take place one service at a time like this, reducing downtime.  
When you finish migrating to Traefik 2, take down the Traefik 1 service. You will then end up with this scenario:

<figure>
![Traefik 2 handling all routing](https://dev-to-uploads.s3.amazonaws.com/i/nkf1xvq3l20t8thiuj8m.png)
<figcaption>Traefik 2 handling all routing</figcaption>
</figure>

## Wrapping Up

And that's it! You’ve successfully migrated from [Traefik][2] 1.x to 2.x one service at a time. This step by step migration strategy comes from the StranglerFigApplication pattern, as [described by Martin Fowler][3]. As a final note, I would highly recommend putting your configurations under version control as that would make it very easy to roll back changes when needed.

--
<em>Cover image by <a href="https://unsplash.com/@heidifin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Heidi Fin</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em>

[1]: https://star-history.t9t.io/#containous/traefik
[2]: https://containo.us/traefik/
[3]: https://martinfowler.com/bliki/StranglerFigApplication.html