Setting up a development environment using Traefik v2

In this post, we explore a basic development environment setup using Traefik.

Basic concepts

Traefik is an open-source Edge Router. “Edge” means that is supposed to live and handle traffic on the edge of your network. It is not supposed to be used in routing internal traffic within your infrastructure but only traffic between external networks (internet) and your infrastructure. Traefik is written in Go and it has the ability to dynamically adjust its configuration. It is monitoring your infrastructure (Docker, Kubernetes, AWS, Mesos,…) for changes and dynamically adjusts its functionality when something changes (auto-discovery).

The most important Traefik components are the following:

Entrypoints: These are the network entry points into Traefik. Think of them as sockets. They define which traffic (ports and protocols) will be intercepted and handled by Traefik.

Routers: They route/forward an incoming request to a service. Routers need to be connected to one or more entrypoints in order to receive the requests that arrive at these entrypoints. “Routers analyze the incoming requests to see if they match a set of rules. If they do, the router might transform the request using pieces of middleware before forwarding them to your services.”

Middlewares: They are attached to a router in order to tweak the request before it is sent to the service or to tweak the response before it is sent back to the client. Traefik provides several pre-defined middlewares (e.g Headers, BasicAuth, RateLimit, CircuitBreaker, Compress,…) grouped by the protocol (HTTP, TCP,…) they can be applied to.

Services: The Services are responsible for configuring how to reach the servers (the actual services – the containers in our case) that will eventually handle the incoming requests. Sometimes, you don’t need to explicitly define the services. Each service has a load-balancer, even if there is only one server to forward traffic to.

If case we use Docker provider, if a label defines a router (e.g. through a router Rule) but no service is defined, then a service is automatically created and assigned to the router. This is our case.

Providers: A Provider links an infrastructure component (orchestrator, container engine, key-value store or even a file) to Traefik so that changes to infrastructure can be detected without restarting Traefik.

The following diagram gives an overview of Traefik architecture:

Our setup

Let’s see an example setup that will contain 2 applications ( https://github.com/agougousis/traefik ). One served through HTTP and one served through HTTPS (using self-signed certificates).

Directory structure

The directory structure of our setup is the following:

Suppose that the path to this directory is D:\myapps\traefik-sample. Create 2 more directories. One for the HTTP application and one for the HTTPS application and place inside some example index.html and index.php files. The directories for those 2 applications will be as follows for this example:

D:\myapps\public_app

     index.html
     index.php

D:\myapps\api_app

     index.html
     index.php

We have put inside an index.html and a, index.php file , for testing purposes.

DNS entries

Add the relevant DNS entries to hosts file:

127.0.0.1 api.mydomain.gr
127.0.0.1 www.mydomain.gr

The first app will be available through HTTP (port 80) at: http://api.mydomain.gr
The second app will be available through HTTPS (port 443) at: https://www.mydomain.gr . If you try to visit it through HTTP, you will get redirected.

Setting up the infrastructure with Docker

The contents of docker-compose.yml will be:

version: '3.3'

services:
    reverse-proxy:
        # The official v2 Traefik docker image
        image: traefik:v2.4
        ports:
            - "80:80"
            - "443:443"
            - "8080:8080"
        volumes:
            # Use a static configuration file instead of command parameters to the container
            - ./traefik/traefik.yaml:/etc/traefik/traefik.yaml
            # Copy traefik logs locally so we can check them out without having to connect to traefik container
            # (where Traefik writes logs is defined in static configuration)
            - ./shared/traefik.log:/etc/traefik/log/traefik.log
            # Traefik provider configuration. Share the docker sock file
            # with traefik container so that Traefik can listen to the Docker events
            # If you are on Linux, replace double slash with single
            - //var/run/docker.sock:/var/run/docker.sock
            # Share files between Traefik container and host.
            - ./shared:/shared
        networks:
            - internet # ports opened by traefik (80, 443, 8080) can be accessed through external network
        labels:
            - "traefik.enable=true"
            - "traefik.http.routers.api.service=api@internal" # Enable the API to be a service to access
    public-app-nginx:
        build:
            context: public_app/nginx
        volumes:
            - ../public_app:/var/www/html
        networks:
            - internet
            - intranet
        depends_on:
            public-app-fpm:
                condition: service_started
        labels:
            - "traefik.enable=true"
            - "traefik.docker.network=internet"
            # Define an HTTPS redirection middleware named 'https-redirect2'
            - "traefik.http.middlewares.https-redirect2.redirectScheme.scheme=https"
            - "traefik.http.middlewares.https-redirect2.redirectScheme.permanent=true"
            # Define a router to redirect HTTP traffic for 'www.mydomain.gr' to HTTPS
            # It will use the middleware we defined earlier.
            - "traefik.http.routers.nginx-web2.entrypoints=web"
            - "traefik.http.routers.nginx-web2.rule=Host(`www.mydomain.gr`)"
            - "traefik.http.routers.nginx-web2.middlewares=https-redirect2"
            # Define a router to direct HTTPS traffic for 'www.mydomain.gr' to this service
            # (self-signed certificates defined in static configuration will be used)
            - "traefik.http.routers.nginx-webs2.entrypoints=websecure"
            - "traefik.http.routers.nginx-webs2.rule=Host(`www.mydomain.gr`)"
            - "traefik.http.routers.nginx-webs2.tls=true"
    public-app-fpm:
        build:
            context: public_app/fpm
        scale: 1
        volumes:
            - ../public_app:/var/www/html
        networks:
            - intranet # fpm server is accessible only by other containers
    api-app-nginx:
        build:
            context: api_app/nginx
        volumes:
            - ../api_app:/var/www/html
        networks:
            - internet # It should be able to talk to traefik and vice versa.
            - intranet
        depends_on:
            api-app-fpm:
                condition: service_started
        labels:
            - "traefik.enable=true"
            - "traefik.docker.network=internet"
            # Define a router to direct HTTP traffic for 'api.mydomain.gr' to this service
            - "traefik.http.routers.nginx-base.entrypoints=web"
            - "traefik.http.routers.nginx-base.rule=Host(`api.mydomain.gr`)"
    api-app-fpm:
        build:
            context: api_app/fpm
        scale: 1
        volumes:
            - ../api_app:/var/www/html
        networks:
            - intranet # fpm server is accessible only by other containers
networks:
    internet:
        external: true
    intranet:
        external: false

* you can ignore the container labels for the moment. We will get back to them in a while.

Traefik static (startup) configuration

The static configuration contains elements that does not change often. You can define the static configuration through a configuration file, some command-line arguments or environmental variables. Here, we chose the first. You can find it in traefik.yaml:

log:
    level: DEBUG
    filepath: "/etc/traefik/log/traefik.log"
accesslog: true
ping: true
api:
    dashboard: true
    insecure: true
    debug: true
entryPoints:
    web:
        address: ":80"
    websecure:
        address: ":443"
providers:
    docker:
        exposedByDefault: false
        network: "internet"
tls:
    certificates:
        - certFile: /certs/selfsigned.crt
          keyFile: /certs/selfsigned.key

As you see, here we configure where the logs will be written in Traefik (reverse-proxy container), what kind of sockets we open to the outside world (TCP sockets on ports 80 and 443), what kind of infrastructure API (here, Docker) Traefik will try to communicate for configuration discovery, any kind of SSL certificates that will be used, etc.

Traefik dynamic configuration

This is the configuration that is retrieved through infrastructure API (Providers). As described in documentation, “the dynamic configuration contains everything that defines how the requests are handled by your system. This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss.” The way dynamic configuration is provided depends on the provider. For Docker it can be done by attaching labels to our containers.

For each container, a service and a router will be created. If we don’t do it through labels, Traefik will automatically do it for us. As you can see in our example, routers are defined like that:

Define a router that handles traffic from an entrypoint
traefik.<protocol>.routers.<routerName>.entrypoints=<entrypoint name>

Apply rules to make a router filter the requests it should handle
traefik.<protocol>.routers.<routerName>.rule=<ruleDescription>

Optionally, middlewares can be attached to each router:

Define & Configure middleware
traefik.<protocol>.middlewares.<middlewareName>.<middlewareType>.<property>=<value>

Attaching middleware
traefik.<protocol>.routers.<routerName>.middlewares=<middlewareName>

A router sends the traffic to the service and the service will forward it to the server (the container). Since we don’t define them in our example, Docker will automatically create a service for our container and it will attach it to the routers defined for this container.

Let’s go live!

Start the containers

$ docker-compose up -d

If you have made changes to some configuration files (e.g Default ), make sure you rebuild the docker images:

$ docker-compose up -d --build

Traefik dashboard will be accessible at: http://localhost:8080/dashboard/#/

Traefik API endpoints will also be available e.g: http://localhost:8080/api/version

If you want to connect to nginx container to check the nginx logs:
$ docker-compose exec public-app-nginx bash

The Dockerfile and the configuration files for Nginx and PHP-FPM can be found here: https://github.com/agougousis/traefik