Traefik vs Nginx for Reverse Proxy with Docker on a Raspberry Pi

By Alex Hyett on in Software Development

I use my Raspberry Pi as my own personal home server. Up until recently, I have been using nginx as a reverse proxy for my docker containers. However, recently I have switched to Traefik and I have found it is much easier to maintain.

I am going to go through the steps needed to set up both and the pros and cons of each.

Prerequisites

I am going to assume you already have a Raspberry Pi set up and able to run docker images. I tend to use Hypriot for all of my Raspberry Pis as it saves setting up docker manually each time.

Docker Networks

I set up docker-compose files for everything I run on my Raspberry Pi, as these can be checked in to git.

When you run docker-compose up it automatically creates an internal network for all of your docker containers in that file. This allows each of the containers to communicate with each other using the service name (e.g. http://servicename:4856).

However, if you have other docker containers running in another docker-compose file it won’t be able to communicate with these containers.

To get around this you need to create an external network. In this example we are going to call our network pi which can be created with the command:

docker network create pi

Then in your docker-compose file, you need to set the network as external at the bottom of the file:

networks:
  pi:
    external: true

You then need to add the network to your containers:

networks:
  - pi

For example, your complete docker-compose.yml file might look like this:

version: '3.4'
services:
  whoami:
    image: 'traefik/whoami'
    ports:
      - 80:80
    networks:
      - pi

networks:
  pi:
    external: true

This is required whether you are using Nginx or Traefik for your reverse proxy.

Setting up Nginx

We are going to run Nginx from docker as well and set up the configuration so we can access whoami from http://localhost/whoami.

You can find a working example on my GitHub page alexhyett/traefik-vs-nginx-docker.

To set up Nginx we simply need to have a location for our service set up in the configuration file. Your config file should look like this:

# HTTP Server
server {
    listen 80 default_server;
    # listen [::]:80 default_server ipv6only=on;
    root   /srv/www;

    # Make site accessible from http://localhost/
    server_name localhost;

    error_log stderr notice;

    #location-start
    location /whoami {
        proxy_pass       http://whoami;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;


        ## Required for websockets
        proxy_http_version 1.1;
        proxy_set_header Connection "upgrade";
        proxy_set_header Upgrade $http_upgrade;
        proxy_read_timeout 600s;

        ## Optional: Do not log, get it at the destination
        access_log off;
    }
    #location-end
}

If you want to add more services you can just copy the location block and change a couple of lines to match your service.

location /whoami

This line specifies the path we want to be able to access the service on. So, in this case, this will be on http://localhost/whoami.

proxy_pass       http://whoami;

This is how the service can be accessed at the moment. If we had whoami running on a different port we would have to specify the port as well. For example, http://whoami:8080.

As we have the docker network set up we can use the service name in the configuration file.

Docker Compose File for Nginx

In our docker-compose file we need to add in a service for nginx and remove the ports for whoami, as these will conflict with the ones for nginx.

version: '3.4'
services:
  nginx:
    build: nginx
    restart: 'unless-stopped'
    networks:
      - pi
    ports:
      - '80:80'
    depends_on:
      - whoami

  whoami:
    image: 'traefik/whoami'
    restart: 'unless-stopped'
    networks:
      - pi

networks:
  pi:
    external: true

Run docker-compose up and you should be able to access whoami from http://localhost/whoami (or replace localhost with your Raspberry Pi IP address if accessing remotely).

Pros

  • Simple to set up with only a little configuration.

Cons

  • Requires manually updating the config file to add new services.
  • Docker container needs to be rebuilt or nginx reloaded to pick up the new services.

Nginx is a good solution if you only have a couple of services and you aren’t planning on changing or adding any new ones that often.

If you add in new services you generally need to rebuild the docker container or at least reload nginx if you make the changes on a volume.

Setting up Traefik

Traefik is a little more involved to set up but you don’t need to manually update config files and rebuild to be able to add new services.

In the first part, I will show you how to get Traefik running locally and then how you can set up it with SSL and password protection. In both examples, I am using Traefik v2. Traefik had quite a few breaking changes between v1 and v2 so you need to make sure you are using v2 for these examples to work.

Our traefik.toml config file looks like this:

[entryPoints]
  [entryPoints.web]
    address = ":80"

[api]
  dashboard = true
  insecure = true

[providers.docker]
  watch = true
  network = "web"
  exposedByDefault = false

This simply sets the entry port for our containers to be port 80 and sets up docker to watch for new containers. I have chosen to set exposedByDefault = false to stop new containers from being automatically exposed. You might want this but I prefer to explicitly set which containers will be exposed to Traefik.

You can find a working example on my GitHub page alexhyett/traefik-vs-nginx-docker.

Docker compose for Traefik

version: '3.4'
services:
  traefik:
    image: 'traefik:2.3'
    container_name: 'traefik'
    restart: 'unless-stopped'
    ports:
      - '80:80'
      - '8080:8080'
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      - './traefik/traefik.toml:/traefik.toml'
    networks:
      - pi

  whoami:
    image: 'traefik/whoami'
    restart: 'unless-stopped'
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.whoami.rule=PathPrefix(`/whoami{regex:$$|/.*}`)'
      - 'traefik.http.services.whoami.loadbalancer.server.port=80'
    networks:
      - pi

networks:
  pi:
    external: true

There are a few things going on here.

First we have the Traefik container which is set up to expose ports 80 and 8080. Port 80 (the HTTP port) is so we can access our services, port 8080 is for the Traefik dashboard.

Traefik needs access to docker to be able to see the running containers which is why we have a reference to docker.sock in the volumes. Lastly for the Traefik container we set up a volume to use our config file we have just created.

For our whoami container to use Traefik we need to set a few labels.

labels:
  - 'traefik.enable=true'
  - 'traefik.http.routers.whoami.rule=PathPrefix(`/whoami{regex:$$|/.*}`)'
  - 'traefik.http.services.whoami.loadbalancer.server.port=80'

The first label tells Traefik to expose this container. The second label sets up how we want this to be routed. In this case it looks for /whoami in the request URL. The last label tells Traefik that this service is on port 80.

If your port is exposed using ports in your docker-compose file and you only have one set up, you don’t need the last line. In our case port 80 is being used for Traefik so we have to set it up this way. I prefer setting up my services this way anyway so that all traffic goes through the reverse proxy instead of having the port directly exposed.

Now when you want to add new services to traefik you just need to add these 3 labels. Don’t forget to change the name of the router, service and path in the last 2 labels.

Pros

  • Requires no additional changes to Traefik once set up
  • Easily add services by adding labels to docker-compose services.

Cons

  • Not as easy to set up advanced configurations that Nginx can do (if you need them).

Setting up Traefik with Password Protection and SSL

If you plan to have your Raspberry Pi services publicly accessible over the internet then you are going to want to add some additional security.

To be able to add security to Traefik we need to remove the insecure mode we added earlier. So in traefik.toml remove the line:

insecure = true

When you aren’t running in insecure mode the dashboard will no longer be available on port 8080 instead you will be able to access it from http://localhost/dashboard/ (or replace localhost with your Raspberry Pi IP address if accessing remotely).

As we are going to add SSL in a bit, it is also worth adding in port 443 to our docker compose. Change the Traefik entry in your docker compose file to look like this:

version: '3.4'
services:
  traefik:
    image: 'traefik:2.3'
    container_name: 'traefik'
    restart: 'unless-stopped'
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      - './traefik/traefik.toml:/traefik.toml'
    networks:
      - pi

Password protection

To generate our hashed password we need to have htpasswd installed. If you don’t have it installed already on your Raspberry Pi you can do so with.

sudo apt-get install apache2-utils

For the super insecure username and password of admin, admin we would type the following. (Note, you definitely need something more secure than this)

htpasswd -nb admin admin

You should see an output like this:

admin:$apr1$86fC1Dr4$tDIyf.Zhg4z.NSf3uHY./.

We are then going to create a new config file for these settings called traefik_dynamic.toml where we are going to add in this new password.

[http.middlewares.simpleAuth.basicAuth]
  users = [
    "admin:$apr1$86fC1Dr4$tDIyf.Zhg4z.NSf3uHY./."
  ]

[http.routers.api]
  rule = "Host(`localhost`)"
  entrypoints = ["web"]
  middlewares = ["simpleAuth"]
  service = "api@internal"

We also need to reference this file at the bottom of traefik.toml

[providers.file]
  filename = "traefik_dynamic.toml"

Lastly, we are going to add in a volume for this file so it gets loaded by Traefik.

volumes:
  - '/var/run/docker.sock:/var/run/docker.sock:ro'
  - './traefik/traefik.toml:/traefik.toml'
  - './traefik/traefik_dynamic.toml:/traefik_dynamic.toml'

Now all we need to do is reload our docker containers:

docker-compose down
docker-compose up

When you navigate to your dashboard at http://localhost/dashboard/ (or replacing localhost with your RPi IP address) you should be prompted to enter in your username and password (which was admin/admin if you used the example above).

However, when we navigate to whoami it is unprotected. If you wanted to protect your services as well you need to add another label to the docker-compose file.

- 'traefik.http.routers.whoami.middlewares=simpleAuth@file'

Now when we restart our docker containers you will be prompted for your username and password to access http://localhost/whoami.

SSL with Lets Encrypt

Now we have password protection we are going to add SSL too using LetsEncrypt.

First, we need to add in the secure endpoint into our traefik.toml file and we are going to set the web endpoint to always redirect to the secure endpoint. Then we are going to add a certificate resolver for Lets Encrypt.

Your final file should look like this:

[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http.redirections.entryPoint]
      to = "websecure"
      scheme = "https"

  [entryPoints.websecure]
    address = ":443"

    [entryPoints.websecure.http.tls]
      certResolver = "lets-encrypt"

[api]
  dashboard = true

[certificatesResolvers.lets-encrypt.acme]
  email = "your_email_here"
  storage = "acme.json"
  [certificatesResolvers.lets-encrypt.acme.tlsChallenge]

[providers.docker]
  watch = true
  network = "web"
  exposedByDefault = false

[providers.file]
  filename = "traefik_dynamic.toml"

You need to change the your_email_here to your actual email address.

Now in our traefik_dynamic.toml file we need to tell Traefik to always use the lets-encrypt resolver for our HTTPS connections.

[http.middlewares.simpleAuth.basicAuth]
  users = [
    "admin:$apr1$86fC1Dr4$tDIyf.Zhg4z.NSf3uHY./."
  ]

[http.routers.api]
  rule = "Host(`yourdomain.com`)"
  entrypoints = ["web","websecure"]
  middlewares = ["simpleAuth"]
  service = "api@internal"
  [http.routers.api.tls]
    certResolver = "lets-encrypt"

You need a valid domain to be able to use lets encrypt which should already be set up to point to your Raspberry Pi’s public IP. You can use subdomains here as well. Change yourdomain.com to your actual domain.

We also need to create an acme.json file which is going to store your certificate. For Traefik to be able to use this file you need to run the following so it has the correct permissions, otherwise Traefik will give you errors that the permissions are too broad.

chmod 600 acme.json

Finally, we need to add the acme.json file as a volume on docker.

volumes:
  - '/var/run/docker.sock:/var/run/docker.sock:ro'
  - './traefik/traefik.toml:/traefik.toml'
  - './traefik/traefik_dynamic.toml:/traefik_dynamic.toml'
  - './traefik/acme.json:/acme.json'

Now all you need to do is start up docker again everything should be working.

You may find when you first load things up you will get an unsigned Traefik certificate but this will be replaced with the LetsEncrypt version once it has been automatically registered. If this doesn’t happen then make sure your email address and domain is valid.


Was this post useful?
If you found this post useful and would like to support me, you can do so by buying me a coffee. Donations help keep this blog ad-free.

ALSO ON ALEXHYETT.COM

Dealing with Imposter Syndrome as a Software Developer

Dealing with Imposter Syndrome as a Software Developer

  • 28 May 2021
I have been a professional software developer for over a decade and I have been writing code for over 25 years. However, sometimes I still…
Using WireMock.net for Integration Testing

Using WireMock.net for Integration Testing

  • 21 May 2021
Last week I showed you how you can use Wiremock in a docker container to mock API calls that your application uses. This week is on a…
Mocking API calls using WireMock

Mocking API calls using WireMock

  • 14 May 2021
It is rare in software development that you are building something in complete isolation from everything else. Generally, you are going to…
Using ngrok to test local websites and APIs

Using ngrok to test local websites and APIs

  • 07 May 2021
Often when I am creating a new website, I want to see how it is going to look on an actual device like my phone or tablet. You can use…
Using GitHub Actions to Deploy to S3

Using GitHub Actions to Deploy to S3

  • 26 March 2021
Recently I went through the process of setting up Drone CI on my Raspberry Pi. The plan was to use my Raspberry Pi as a build server for…
Getting Started with AWS Step Functions

Getting Started with AWS Step Functions

  • 12 March 2021
I have recently been looking into AWS Step Functions. For those not familiar with them, Step Functions are Amazon’s way of providing a state…
Useful Docker Commands Worth Saving

Useful Docker Commands Worth Saving

  • 12 February 2021
I use docker every day. All the applications I write at work or at home end up in docker containers. Most of the time though, I am only…
Grafana Monitoring on a Raspberry Pi

Grafana Monitoring on a Raspberry Pi

  • 28 January 2021
As you might have seen from my last few posts I have quite a lot running on my Raspberry Pi. I am currently using a Raspberry Pi 2 B which…

Alex Hyett
WRITTEN BY

Alex Hyett

Software Developer, Entrepreneur, Father, and Husband. Engineering Lead at Checkout.com.

Want to get in touch? You can find me here:


Join the Newsletter

Subscribe to get my latest content by email.

    I won't send you spam. Unsubscribe at any time.