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

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

by | 10 min read
Published:
Updated:

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.

🚀 Are you looking to level up your engineering career?

You might like my free weekly newsletter, The Curious Engineer, where I give career advice and tackle complex engineering topics.

📨 Don't miss out on this week's issue

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.

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/ .

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/ 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 helpful? If you want to say thanks, I love coffee ☕️ , any support is appreciated.


ALSO ON ALEXHYETT.COM

What is Event Driven Architecture?

What is Event Driven Architecture?

  • 14 April 2023
One of the leading architecture patterns used with microservices is event-driven architecture. Event-driven architecture has many benefits…
Hosting n8n for Free with Railway

Hosting n8n for Free with Railway

  • 30 January 2023
I have been using n8n for a couple of months now, and it has allowed me to automate so much of my daily workflow. These are some of the…
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…
How to set up Drone CI on Raspberry Pi (and why you shouldn't)

How to set up Drone CI on Raspberry Pi (and why you shouldn't)

  • 27 January 2021
I wanted to put together my home build server using my Raspberry Pi. After looking at the options I picked Drone CI, it has a nice interface…
Hosting a Secure Static Website on AWS S3 using Terraform (Step By Step Guide)

Hosting a Secure Static Website on AWS S3 using Terraform (Step By Step Guide)

  • 14 January 2021
By the time you finish reading this article, you will know how to get your static websites up and running securely on AWS using Terraform…