Understand File Provider in Traefik 2

Ever since I started making videos of using Traefik 2, I got a few requests to make a video talking about Traefik 2 configuration in general. As my understanding of this product could be wrong or even misleading, I am very careful NOT to tell people what they SHOULD do, instead I simply provide a working solution for people to explore this product.

Because of the requests I’ve got, I feel like I should know more about this product in case someone ask me. This article is about file provider, something I got it working recently.

If you’ve watched my video before, you may remember the providers section I put on my static configuration traefik.yml as below:

1
2
3
4
5
6
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: /configurations/dynamic.yml

Although users can define all setting with just labels, I always prefer using less labels. It looks really messy to have too many labels IMO. That’s why I use a combination of docker with file provider to set up my system previously. Docker and labels are used to define router,service and file was used to definemiddleware.

By using only the file provider to define router, middleware and service does not only help reducing labels but also bring in some extra features like swapping different host names to different services dynamically without the need of restarting your container.

Most settings with file providers are dynamic. In this article, I will focus on how these settings are working together instead of how each settings work. Users need to change the setting to match their environment and applications.

Let’s begin:

Preparations

Static Configuration

We must define entryPoints and provider on static configuration traefik.yml.
You can use my config as a reference for entryPoints. Based on office documentation, we can use Traefik to either monitor a Single file or all config files in a directory with file provider. It is pretty self-explanatory, examples below:

Single File

1
2
3
4
providers:
file:
filename: /path/to/config/dynamic_conf.yml
watch: true

Directory

1
2
3
4
providers:
file:
directory: /path/to/config
watch: true

Dynamic configuration

Let’s see how we use dynamic configuration to replace labels.

Single File

Well, the name says it all. We just put all settings on 1 single file. I found below example from middleware page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# As YAML Configuration File
http:
routers:
router1:
service: myService
middlewares:
- "foo-add-prefix"
rule: "Host(`example.com`)"

middlewares:
foo-add-prefix:
addPrefix:
prefix: "/foo"

services:
service1:
loadBalancer:
servers:
- url: "http://127.0.0.1:80"

This setting is pretty straight forward. When clients go to example.com, Traefik will use foo-add-prefix middleware on their requests and then direct them to myService which internal ip address is 127.0.0.1 port 80.

Directory

If you use directory, you can use multiple config files for settings. For example, router.yml for router, middleware.yml for middlewares etc. Below is just an example of the folder structure.

1
2
3
4
5
6
7
8
9
|-- data
| |-- configurations
| | |-- middlewares.yml
| | |-- tls.yml
| | |-- router.yml
| | `-- services.yml
| |-- acme.json
| `-- traefik.yml
`-- docker-compose.yml

For personal or small websites, single file is sufficient. If you are managing something complicated, it is probably easier to mange your config settings on different files.

Config Example

When we use file provider, we can start the service first, and then define how we want to handle traffic to this service later. Let me use a Nginx service as an example.

Docker-Compose

As you can see I am not using any labels here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.7'

services:
nginx:
image: nginx:alpine
container_name: nginx
restart: always
volumes:
- ./public:/usr/share/nginx/html:ro
networks:
- proxy

networks:
proxy:
external: true

Let’s run docker-compose up -d to create this container first.

Router

The official document did a good job explaining the router rules. Most of time you just use Host(`example.com`, ...) to define the host name of this router. If you want route your traffic to a subfolder under the base domain, you can use Host(`example.com`, ...) && Path(`/traefik`). In my example, I route all requests of nginx.yourdomain to nginx-service and apply a middleware called user-auth to it.

1
2
3
4
5
6
7
8
9
http:
routers:
dashboard:
service: nginx-service
middlewares:
- "user-auth"
entryPoints:
- "websecure"
rule: "Host(`nginx.yourdomain`)"

Let me compare the labels we would use to achieve the same result. IMO the above config is much more readable.

1
2
3
4
5
6
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.nginx-secure.entrypoints=websecure"
- "traefik.http.routers.nginx-secure.middlewares=user-auth@file"
- "traefik.http.routers.nginx-secure.rule=Host(`nginx.yourdomain`)"

Middleware

You might have seen me using middleware in all my previous articles and videos. I always use dynamic configuration to define my middlewares because I consider middlewares as tools and tools should be easy to use and remove.

You might have noticed I used middleware@file on label. If you use middleware on dynamic configuration, you can just use it.

Here are the middlewares I am using, please feel free to check and use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
http:
middlewares:
# HSTS header. I use it to get A+ rating from SSLLabs
secureHeaders:
headers:
sslRedirect: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
# Basic auth. Traefik Dashboard needs it
# Username: Admin
# Password: qwer1234
user-auth:
basicAuth:
users:
- "admin:$apr1$tm53ra6x$FntXd6jcvxYM/YH0P2hcc1"
# Redirect traffic from base domain to www
www-redirect:
redirectRegex:
regex: "^https://yourdomain/(.*)"
replacement: "https://www.yourdomain/${1}"
permanent: true

# This middleware is needed with Bitwarden websocket
bw-stripPrefix:
stripPrefix:
prefixes:
- "/notifications/hub"
forceSlash: false

Service

I was stuck with defining service before. I did not understand where I can get the url from demonstrated on official document. I don’t need to worry about what ip address/url it is when I am using labels. It just work when when I am using below labels.

1
2
- "traefik.http.routers.nginx-secure.service=nginx-service"
- "traefik.http.services.nginx-service.loadbalancer.server.port=80"

Even on the offical documentation it says:

In general when configuring a Traefik provider, a service assigned to one (or several) router(s) must be defined as well for the routing to be functional.
There are, however, exceptions when using label-based configurations:

  1. If a label defines a router (e.g. through a router Rule) and a label defines a service (e.g. implicitly through a loadbalancer server port value), but the router does not specify any service, then that service is automatically assigned to the router.
  2. 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.

It means even if we don’t use any lables to define any service, Traefik will create and assign the service to my router automatically.

So how should I find the IP address of the service we created? I found two methods and the second method was deprecated on the official forum. I still write it on my article as a reference.

Use container name(Preferred)

Service discovery in docker network is a better option.

When containers are running in the same network, the other containers can find this service via their container name. So when this container restarted with a new ip address, traefik can still find them via their dns name within the network.

Docker Inspect

If you still remember, we started a Nginx container at the beginning of our config example. We can use docker inspect <container_id> to check its internal ip.

  1. Let’s use docker ps to check all running containers. Let’s say we can see our Nginx container id is 41fb11d0047c
  2. Then we run docker inspect 41fb11d0047c. You will get a lot of information about this container, what we care about is its NetworkSettings .
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
          "NetworkSettings": {
    ...
    "Ports": {
    "80/tcp": null
    },
    ...
    "IPAddress": "192.168.48.4",
    ...
    }
    }
    }
    As we can see, the internal ip address is 192.168.48.4 and port 80 is opened. Therefore we can define our nginx-service as below:
    1
    2
    3
    4
    5
    6
    http:
    services:
    nginx-service:
    loadBalancer:
    servers:
    - url: "http://192.168.96.4:80"

Use Traefik Dashboard

This is NOT recommended on official forum

  1. Add both docker and file provider on traefik.yml.
    1
    2
    3
    4
    5
    6
    providers:
    docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    file:
    filename: /configurations/dynamic.yml
  2. After we starting a docker container, we will find this service listed on Traefik Dashboard. We can then grab the internal ip and port from dashboard.
    nginx-service

P.S It really took me a few days to test and write this article. I hope did not make too many mistakes and you find this article useful. My next task will be making a video for this article. It is a lot easier to watch a video than reading a long article after all.

Anyway, thanks for reading.

See you next time