Posted on ::

Intro

This is a part of series of posts about Traefik and its configuration.

More Recipes

We can use entities, defined via Docker labels, in multiple places. I will be taking advantage of this defining all possible middlewares in Traefik service. To allow that, one small adjustment is needed. We should include Traefik own service in label analysis:

  traefik:
    #...
    labels:
      traefik.enable: true
    #...

Compression

Traefik, like any other reverse proxy, can compress proxied data. Use Compress middleware for this purpose.
Let's define a middleware by adding some labels to Traefik service:

  traefik:
    #...
    labels:
      #...
      traefik.http.middlewares.compress-all.compress: true
      #...
    #...

And then, we can use it for Whoami service:

  whoami:
    #...
    labels:
      #...
      traefik.http.routers.whoami.middlewares: compress-all
      #...
    #...

Re-deploy, hit F5 and...

And... This isn't working? There's must be a header... content-encoding or something...
Something's wrong?

The size. It matters. In this particular case at least.
Compress middleware has an option called minResponseBodyBytes. It defines a minimum threshold for compression to kick in. Responses, smaller than this value won't be compressed. Default value is 1024 bytes which is slightly more than our response. Traefik just skipped it.

compression, or rather lack of it

compression, or rather lack of it

To see middleware in action let's reduce minResponseBodyBytes to 1 byte and see what happens:

  traefik:
    #...
    labels:
      #...
      traefik.http.middlewares.gzip.compress.minresponsebodybytes: 1
      #...
    #...

Now, redeploy the stack and check again.

compression working, brotli in this case

compression working, brotli in this case

Don't forget to remove this little hack afterward.
Trying to compress small responses, you may end up with bigger chunks of data. And compressing only things, that need to be compressed, saves CPU cycles to both - server and a client.
Consider this a good practice.

Redirect, Remove WWW, Two Domains - same Service

Handling single domains like whoami.localhost is straightforward. Add whoami.localhost into your service's router rule, and be done.
Oftentimes it is required to deal with two domains:

  • canonical like whoami.localhost;
  • its CNAME, like www.whoami.localhost.

Required behavior all times the same:

  • accepted request on www;
  • redirect client to non-www;
  • make sure it was a 301 redirect.

This can be achieved using redirectregex middleware.

Let's declare a middleware and some of its options:

  traefik:
    #...
    labels:
      #...
      traefik.http.middlewares.wwwbegone.redirectregex.regex: '^https://www\.(.*)'
      traefik.http.middlewares.wwwbegone.redirectregex.replacement: 'https://$${1}'
      traefik.http.middlewares.wwwbegone.redirectregex.permanent: true
      #...
    #...

The options used:

  • regex defines regex which will be used to match request URL;
  • replacement defines how to modify request URL;
  • permanent instructs Traefik to use 301 redirect instead of 302, which will be used by default.

And these are service labels.

  whoami:
    #...
    labels:
      #...
      traefik.http.routers.whoami.rule: Host(`whoami.localhost`) || Host(`www.whoami.localhost`)
      traefik.http.routers.whoami.middlewares: wwwbegone
      #...
    #...

Note highlighted line. There goes both domains, we need service answer on. More on this here.

Apparently, you can write you rule like that:
Host(`foo.local`,`foo-status.local`,`bar-status.local`)
and it will still work.

Now redeploy the stack. Domain whoami.localhost should open as usual, but when you try to access www.whoami.localhost redirect will happen.

recieved 302 redirect

recieved 302 redirect

landed on non-www domain

landed on non-www domain

Basic HTTP authentication

Another common thing is Basic HTTP Authentication. It can be implemented with basicauth middleware.

Defining middleware will be something like this:

  traefik:
  #...
    labels:
      #...
      traefik.http.middlewares.keepthemaway.basicauth.users: "nmm2077:$$apr1$$q/wpkUkO$$3RLAT4KydMB1FteLOcIx90"
      # traefik.http.middlewares.keepthemaway.basicauth.usersfile: "/path/to/my/usersfile"
      #...
  #...

You can only use users or usersfile option, but not both.

Value for users setting consists of username, written in plain text, and a hashed password. MD5, SHA1, SHA256, SHA512 or BCrypt will do. If you want more than one user - write them one by one, delimiting pairs by a comma.
Password hashes can be obtained in various ways. My go to is to use openssl:

openssl passwd -apr1

Password:
Verifying - Password:

$apr1$Jw/8DYLd$UEJTaPcqZCdpLGyMqn5Qr0

The string $apr1$Jw/8DYLd$UEJTaPcqZCdpLGyMqn5Qr0 is our password hash.

Docker Compose won't let me use $ symbol, will it?

No, it won't.
It'll assume you're trying to expand an environment variable or something.
Prepend every $ with another $. It will work as escape character.
Correct value for compose.yml will be:
$$apr1$$Jw/8DYLd$$UEJTaPcqZCdpLGyMqn5Qr0.

To use this middleware add it to service middleware list, as usual:

  whoami:
    #...
    labels:
      #...
      traefik.http.routers.whoami.rule: Host(`whoami.localhost`)
      traefik.http.routers.whoami.middlewares: keepthemaway
      #...
    #...

Let's see it in action.

password request dialogue, I already filled login and password in

password request dialogue, I already filled login and password in

firefox warns about potential security risk

firefox warns about potential security risk

request headers

request headers

If you decide to go with passwords in a file (usersfile option) utility from apache2-utils called httpasswd can be used to generate required file.

# crezter file
htpasswd -c /path/to/.htpasswd username
# add another user
htpasswd /path/to/.htpasswd another_username

Each line of file will represent different user credentials. And you don't need to escape $ signs here. In our case content of .htpasswd file will be:

nmm2077:$apr1$Jw/8DYLd$UEJTaPcqZCdpLGyMqn5Qr0

And only thing needs to be adjusted - is Traefik service labels:

  traefik:
  #...
    labels:
      #...
      # traefik.http.middlewares.keepthemaway.basicauth.users: "nmm2077:$$apr1$$q/wpkUkO$$3RLAT4KydMB1FteLOcIx90"
      traefik.http.middlewares.myauth.basicauth.usersfile: /.htpasswd
      #...
    volumes:
      #...
      - ./.htpasswd:/.htpasswd
      #...
  #...

And that's it.

Hey, what's the password was?

TheStarsWillAidHerEscape

The End??

And this was it! For now, at least.
I'm pretty sure there's something more left in my notes, though.

Table of Contents