Installation and Configuration of Traefik in Docker

Installation and Configuration of Traefik in Docker

Overview

Traefik is a reverse proxy which listens to docker events and can dynamically create backends and insert middleware just by including the appropriate docker labels in subsequent docker-compose.yml files.

Dependencies

Prior to setting up Traefik, a wildcard certificate for the domain will need to be obtained. In this example, we are using *.weepylabs.com, however this is just an example.

NOTE: It is possible to set traefik up to dynamically generate SSL certificates via Let’sEncrypt, but that is not covered in this article.

Installation

On the docker server where you wish to set Traefik up, you will first need to create a docker-compose.yml file:

version: '3'

services:
  traefik:
    image: traefik:v2.9
    container_name: traefik
    command:
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=web-secure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.web-secure.address=:443"
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.file.directory=/certs/"
      - "--providers.file.watch=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certs:/certs
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.weepylabs.com`)"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.service=api@internal"
    networks:
      - traefik_frontend
    restart: always

networks:
  traefik_frontend:
    external: true

Breaking down this config, here are the important bits:

Command

- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=web-secure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web-secure.address=:443"

This sets the web port to 80, the web-secure port to 443. This also sets up redirection from http to https.

- "--providers.docker=true"
- "--providers.file.directory=/certs/"
- "--providers.file.watch=true"

This tells Traefik to use docker as a provider for service discovery. This also says to set up a file watcher in the /certs directory, so we can dynamically load in certificates and configurations.

Volumes

volumes:
  - /etc/localtime:/etc/localtime:ro
  - /var/run/docker.sock:/var/run/docker.sock
  - ./certs:/certs

/etc/localtime is mounted to the container so it can determine system time.

/var/run/docker.sock is mounted so that Traefik has access to the underlying docker stack for service discovery.

./certs is mounted so that we have a place to put in certificates/configurations dynamically.

Labels

labels:
  - "traefik.http.routers.traefik.rule=Host(`traefik.weepylabs.com`)"
  - "traefik.http.routers.traefik.tls=true"
  - "traefik.http.routers.traefik.service=api@internal"

This is where the Traefik magic happens. Using these labels, we can tell traefik we want it to reverse proxy this service.

"traefik.http.routers.<docker-compose-app-name>.rule=Host(`<domain>`)"

This tells traefik that we want to match the Host header with . It is also important that with these labels, you use the same app name from the docker-compose.yml file in the router definition.

"traefik.http.routers.<docker-compose-app-name>.tls=true"

This tells traefik that we want this to secure the endpoint with TLS (SSL).

"traefik.http.routers.<docker-compose-app-name>.service=api@internal"

For Traefik specifically, this is linking the service to an internal traefik service so it knows to serve the traefik dashboard. This is not typically required.

Network

    networks:
      - traefik_frontend
    ...

networks:
  traefik_frontend:

It is imperative that these two values match. What we are doing here is defining the main docker network that Traefik will be communicating on. This means that we will need to add this network to every docker container we wish to put Traefik in front of.

Creating Certificate Store

We wish to serve all services with a wildcard TLS certificate. We will need to set up a default certificate configuration. Create the file certificates.yaml and populate it with the wildcard certificate and key:

tls:
  stores:
    default:
      defaultCertificate:
        certFile: /certs/star.weepytests.com.crt
        keyFile: /certs/star.weepytets.com.key

Now, create a certs directory and place these files in it:

mkdir certs
mv certificates.yaml certs/
mv star.weepytests.com.crt certs/
mv star.weepytests.com.key certs/

When the container starts, it will mount this certs directory and make the certificates and configuration available to docker.

Running Traefik

Now that the configuration is complete, we can bring this up by running docker-compose up -d. Once the container is online, we should be able to navigate to the hostname we provided in the config (in my exmaple, traefik.weepylabs.com) and be greeted with the Dashboard.

Adding new services to Traefik

Now that Traefik is up and running, we are going to want to bring a new service into the fold. In this example, I am using phpldapadmin/openldap service that I wish to make available to Traefik.

Original Configuration

version: '2'
services:
  openldap:
    image: osixia/openldap:1.5.0
    container_name: openldap
    environment:
      LDAP_LOG_LEVEL: "256"
      ...
    tty: true
    stdin_open: true
    volumes:
      ...
    ports:
      - "389:389"
      - "636:636"
    domainname: "weepylabs.com"
    hostname: "wl-dc01"
    restart: always

  phpldapadmin:
    image: osixia/phpldapadmin:latest
    container_name: phpldapadmin
    environment:
      ...
    ports:
      - "8080:80"
    depends_on:
      - openldap
    restart: always

volumes:
  ...

NOTE: Some of the content has been redacted for brevity.

Originally, this docker-compose.yml file brings up two containers, one serves ldap/ldaps, and the other is a web frontend which connects back to the ldap server. There are some environment variables, a couple volume mounts. Running docker-compose up -d would bring this online, but it would not offer a secure connection, and we would have URL’s with port definitions in them, and overall not a great implementation. We want to put the web frontend behind Traefik, but we want to leave the ldap server alone, as it needs to serve ldap/ldaps connections to other hosts on the network and not be interfered with.

Adding Traefik

The first thing that we will need to deal with, is that the frontend container still needs to communicate with the openldap container. Additionally, we need to place the frontend container in the Traefik network. If we only put the frontend container in the traefik network, it will lose it’s ability to communicate with the ldap container. If we put both on the traefik network, traefik will try to reverse-proxy the ldap server connections, which we do not want.

What we need to do, is define two networks:

  • traefik_frontend is the named network from our traefik docker-compose.yml file, which will be the frontend for our web application
  • backend will be a network that the ldap server and our web application can communicate on, which is local to the two of them

At the end of the docker-compose.yml, we will add this:

networks:
  traefik_frontend:
    external: true
  backend:

By indicating that external: true for the traefik_frontend network, we are saying that this network exists external to this docker-compose.yml file. Creating a backend network with no definition indicates we wish to create a local network for these containers.

Now, in the openldap container, we will add this network definition so that the container knows it needs to start only on the backend network:

    ...
    hostname: "wl-dc01"
    networks:
      - backend
    restart: always

In our web app (phpldapadmin), we will need to add both networks, so it knows it needs to be in the traefik network to accept requests externally, as well as be on the same network to access ldap.

    ...
    depends_on:
      - openldap
    networks:
      - traefik_frontend
      - backend
    restart: always

Now that the networks have been set up, we can actually remove the exernal port dependancy on the web-app, as traefik will be managing this connection:

ports:
  - 80

Finally, we need to add the traefik labels to the frontend container, so that traefik knows it should be proxying this service:

labels:
      - "traefik.http.routers.phpldapadmin.rule=Host(`ldap.weepylabs.com`)"
      - "traefik.http.routers.phpldapadmin.tls=true"
      - "traefik.docker.network=traefik_frontend"

This tells traefik the hostname we should be listening on (in this case, ldap.weepylabs.com)

"traefik.http.routers.phpldapadmin.rule=Host(`ldap.weepylabs.com`)"

This indicates that we want it to be TLS

"traefik.http.routers.phpldapadmin.tls=true"

Finally, we want to tell traefik which of the two networks we want this configured on

"traefik.docker.network=traefik_frontend"

Final Configuration

Putting it all together, our docker-compose.yml file will now look like this (brevity redactions are still in place):

version: '2'
services:
  openldap:
    image: osixia/openldap:1.5.0
    container_name: openldap
    environment:
      LDAP_LOG_LEVEL: "256"
      ...
    tty: true
    stdin_open: true
    volumes:
      ...
    ports:
      - "389:389"
      - "636:636"
    domainname: "weepylabs.com"
    hostname: "wl-dc01"
    networks:
      - backend
    restart: always

  phpldapadmin:
    image: osixia/phpldapadmin:latest
    container_name: phpldapadmin
    environment:
      ...
    ports:
      - "8080:80"
    labels:
      - "traefik.http.routers.phpldapadmin.rule=Host(`ldap.weepylabs.com`)"
      - "traefik.http.routers.phpldapadmin.tls=true"
      - "traefik.docker.network=traefik_frontend"
    depends_on:
      - openldap
    networks:
      - traefik_frontend
      - backend
    restart: always

volumes:
  ...

networks:
  traefik_frontend:
    external: true
  backend: