+++ 
title = "How to install Traefik 2.x on a Docker Swarm"
date = "2019-10-21"
author = "Aloïs Micard"
authorTwitter = "" #do not include @ 
cover = ""
tags = ["Docker Swarm", "Dev Ops"]
keywords = ["", ""]
description = ""
showFullContent = false 
+++

I have recently migrated my production docker swarm from Traefik 1.7 to Traefik 2.0 and since I cannot found a good
tutorial I have decided to write one. So in this tutorial you'll learn how to deploy Traefik with HTTPS support on a
docker swarm.

Please note that I won't explain what Traefik is since it may needs his own article and I will focus on the deployment
and configuration. This tutorial will also assume that you have a working docker swarm.

This tutorial will be part of a series regarding Docker Swarm, I'll write other articles to explain how to expose
Traefik dashboard securely, deploy Portainer, etc...

# Install Traefik

Please note that Traefik will need to be deployed on a manager node on your swarm. You'll also need to make sure that
your firewall on this node is correctly setup to allow both port 80 and 443 (http / https) from outside. This is
important because Traefik will listen on these ports for incoming traffic.

The first thing before creating the config file is to create a docker swarm network that will be used by Traefik to
watch for services to expose. This can be done in one command:

```
docker network create --driver=overlay traefik-public
```

This will create an [overlay network](https://docs.docker.com/network/overlay/) named 'traefik-public' on the swarm.

Now we are going to create the docker compose file to deploy Traefik.

```yaml
version: '3'

services:
  reverse-proxy:
    image: traefik:v2.3.4
    command:
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik-public"
      - "--entrypoints.web.address=:80"
    ports:
      - 80:80
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager

networks:
  traefik-public:
    external: true
```

This is the minimal amount of config needed to deploy a working Traefik instance.

----

Let me explain in details what's going on:

```yaml
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
```

Here we are telling to Traefik to listen to the unix docker socket.

```yaml
- "--providers.docker.swarmMode=true"
```

Enable swarm mode support by setting swarmMode to true.

```yaml
- "--providers.docker.exposedbydefault=false"
```

Little security: tell Traefik to not expose container by default: only expose container that are explicitly enabled (
using label traefik.enabled).

```yaml
- "--providers.docker.network=traefik-public"
```

Tell Traefik to dial with exposed containers using traefik-public network. (the one created earlier)

```yaml
- "--entrypoints.web.address=:80"
```

Create an entrypoint named **web** exposed on port 80. This entrypoint will be used to indicate on which port your
application should be exposed.

This configuration is enough to get started. You can deploy Traefik using the following command:

```sh
docker stack deploy traefik -c traefik.yaml
```

Great. Now we will deploy something.

# Deploy and expose a hello-world container

Now it's time to deploy something on your swarm to test the configuration. For this example we are going to deploy
tutum/hello-world a little container with an apache service that display an "Hello World" page.

```yaml
version: '3'
services:
  helloworld:
    image: tutum/hello-world:latest
    networks:
      - traefik-public
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.helloworld.rule=Host(`helloworld.local`)"
        - "traefik.http.routers.helloworld.entrypoints=web"
        - "traefik.http.services.helloworld.loadbalancer.server.port=80"
networks:
  traefik-public:
    external: true
```

The labels sections is read by Traefik to get the configuration of the container and to create the needed components to
expose it.

---

```yaml
- "traefik.enable=true"
```

This flag tells Traefik to expose the container, needed because we explicitly disable container auto exposition with
providers.docker.exposedbydefault.

```yaml
- "traefik.http.routers.helloworld.rule=Host(`localhost`)"
```

Create a Host Matching rule. Here we tell Traefik to redirect all traffic coming with host localhost to this container.

```yaml
- "traefik.http.routers.helloworld.entrypoints=web"
```

Tells Traefik that this container will be exposed using the web entrypoint. (the name correspond with the entrypoint
previously created in Traefik).

```yaml
- "traefik.http.services.helloworld.loadbalancer.server.port=80"
```

Indicate to Traefik that the container expose the port 80 internally. This is needed by Traefik to act as a proxy.

---

Now if you access the page at http://localhost you'll be redirect to Traefik that will proxify the content of the
helloworld container.

# Add HTTPS support

If you have followed this tutorial carefully you should now have a working Traefik instance and a helloworld service
running and accessible on http://localhost. This is working but not secure: you should always use HTTPS when possible.
Thankfully this can be done easily in Traefik.

In this tutorial we are going to use the [HTTP challenge](https://letsencrypt.org/docs/challenge-types/) to
automatically generate a Letsencrypt certificate.

I won't go in the details to explain how the HTTP-01 challenge work, but basically all you have to do is to add/update
the A record of your DNS zone to point to your docker swarm manager IP address. (where Traefik is exposed).

```yaml
version: '3'

services:
  reverse-proxy:
    image: traefik:v2.3.4
    command:
      # Docker swarm configuration
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik-public"
      # Configure entrypoint
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      # SSL configuration
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencryptresolver.acme.email=user@domaine.com"
      - "--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - 80:80
      - 443:443
    volumes:
      # To persist certificates
      - traefik-certificates:/letsencrypt
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
volumes:
  traefik-certificates:
networks:
  traefik-public:
    external: true
```

---

I will explain the new parts:

```
- "--entrypoints.websecure.address=:443"
```

Create an entrypoint named **websecure** exposed on port **443** (https). This entrypoint will be used to indicate on
which port your application should be exposed.

```yaml
- "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
```

Tell Traefik that the challenge named **letsencryptresolver** will use the http challenge.

```yaml
- "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
```

We are going to expose the http challenge over http (obvious since we have no certificate yet)

```yaml
- "--certificatesresolvers.letsencryptresolver.acme.email=user@domaine.com"
```

The email used to create a letsencrypt account

```yaml
- "--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json"
```

Where do Traefik will persist the certificate. This location should be bound to
a [volume](https://docs.docker.com/storage/volumes/) to be persisted between container restart. Here we are saving to
/letsencrypt/ directory, who is mounted as volume traefik-certificates (see traefik.yaml for details)

## Update hello-world to use HTTPS config

Now we are going to update the hello-world deployment configuration file in order to expose it using HTTPS.

```yaml
version: '3'
services:
  helloworld:
    image: tutum/hello-world:latest
    networks:
      - traefik-public
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.helloworld.rule=Host(`helloworld.local`)"
        - "traefik.http.routers.helloworld.entrypoints=websecure"
        - "traefik.http.routers.helloworld.tls.certresolver=letsencryptresolver"
        - "traefik.http.services.helloworld.loadbalancer.server.port=80"
networks:
  traefik-public:
    external: true
```

---

There is only two things to change:

```yaml
- "traefik.http.routers.helloworld.entrypoints=websecure"
```

To indicate to Traefik that we want to expose our container using the previously declared websecure entrypoint.

```yaml
- "traefik.http.routers.helloworld.tls.certresolver=letsencryptresolver"
```

To specify which certificate resolver we wanna use. Here we are using the letsencryptresolver declared before.

---

Easy right? Once the stack is redeployed Traefik will then ask Letsencrypt to generate a SSL certificate for the domain
helloworld.local and Traefik will use it when exposing your application.

N.B: Of course this example won't work since you cannot proove that you own the helloworld.local domain. (.local is a
reserved TLD used for local area network)

## Bonus: Create an automatic HTTPS redirect

If you want to redirect all HTTP traffic to HTTPS it can be done by easily:

```yaml
command:
  ...
  # Global HTTP -> HTTPS
  - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
  - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
```

It will create a global redirection from all HTTP traffic to HTTPS.

---

Here's the final stack to deploy a Traefik instance with HTTPS support and HTTP -> HTTPS global redirection

```yaml
version: '3'

services:
  reverse-proxy:
    image: traefik:v2.3.4
    command:
      # Docker swarm configuration
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.swarmMode=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik-public"
      # Configure entrypoint
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      # SSL configuration
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencryptresolver.acme.email=user@domaine.com"
      - "--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json"
      # Global HTTP -> HTTPS
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
    ports:
      - 80:80
      - 443:443
    volumes:
      - traefik-certificates:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - traefik-public
    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.traefik.loadbalancer.server.port=888" # required by swarm but not used.
        - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
        - "traefik.http.routers.http-catchall.entrypoints=web"
        - "traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker"
        - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
volumes:
  traefik-certificates:
networks:
  traefik-public:
    external: true
```

Long live Docker Swarm and Happy hacking !
