How to set-up Traefik as a reverse proxy for Docker containers

When using docker for managing services, one important thing is how to publish the service to the internet, this typically requires configuring a domain, obtaining an  SSL certificate, and other related tasks. However, performing these tasks manually can be tedious and time-consuming.

The common approach for publishing service

Nginx is a widely-used open-source web server frequently used as a reverse proxy server. The standard configuration for using Nginx as a reverse proxy outside of Docker typically involves defining an upstream server block that specifies the IP address and port number of the backend server and then using the proxy_pass directive to forward incoming requests to the backend server.

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

When using Docker, if Nginx and the container are connected to the same Docker network, Nginx can communicate with the container using the container's hostname. Therefore, the configuration could be rewritten to this:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend-container:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Service publishing with Traefik

Traefik is a modern reverse proxy that is designed for making deploying services simple. It can use service discovery to configure the services and can work with Let's Encrypt to automatically generate certificates.

docker-compose.yml:

version: '3.9'

services:
    traefik:
        image: "traefik:v2.6"
        container_name: "traefik"
        ports:
            - "80:80"
            - "443:443"
            - "8080:8080"
        volumes:
            - "./traefik.toml:/etc/traefik/traefik.toml"
            - "./letsencrypt:/letsencrypt"
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
        networks:
            outbound:
                name: outbound

traefik.toml:

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

  [entryPoints.web.forwardedHeaders]
    insecure = true

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

  [entryPoints.websecure]
    address = ":443"

[certificatesResolvers.letsencrypt.acme]
  email = "janedoe@example.com"
  storage = "/letsencrypt/acme.json"
  [certificatesResolvers.letsencrypt.acme.tlsChallenge]

[api]
  dashboard = true
  insecure = true

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

Finally, you need to create an empty letsencrypt/acme.json file. Then when we want to publish the service, we can do the following:

version: '3.9'

services:
    backend:
        image: backend
        labels:
            - traefik.enable=true
            - traefik.docker.network=outbound
            - traefik.http.routers.backend.rule=Host(`example.com`)
            - traefik.http.routers.backend.entrypoints=websecure
            - traefik.http.routers.backend.tls.certresolver=letsencrypt
        networks:
            - outbound


networks:
    outbound:
        external: true
        name: outbound