Host & Port: Where is it?

In the previous post Traefik was compared to NGINX. Now let’s take a look at a few simple Traefik setups. We’ll focus on specifying the host and port.

NGINX Server on 127.0.0.1

Below is a docker-compose.yml file which specifies two services:

  • web and
  • traefik.

The web service uses NGINX to serve a simple HTML page (shared with the container via a volume mount).

version: '3'

services:
  web:
    image: nginx:alpine
    container_name: nginx
    labels:
      - "traefik.http.routers.web.rule=Host(`localhost`)"
      - "traefik.http.services.web.loadbalancer.server.port=80"
    volumes:
      - ./hello.html:/usr/share/nginx/html/index.html:ro

  traefik:
    image: traefik:v2.5
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

You’ll see a typical pattern in docker-compose.yml files invoking Traefik:

  1. The Traefik service is started with a number of command line arguments specified via command.
  2. The proxied service is started with a number of labels specified via labels (these are analogous to LABEL in a Dockerfile but applied at run time rather than build time).

Let’s dissect the options passed to Traefik on the traefik service:

  • --api.insecure=true — Enable Traefik’s API and Dashboard on an insecure connection (HTTP). Useful for debugging or development environments, but for production environments, it’s recommended to use HTTPS and authentication.
  • --providers.docker=true — Enable the Docker provider, allowing Traefik to automatically discover and manage services based on Docker containers. Traefik listens to Docker daemon events, automatically detects containers and services, and applies routing rules based on their configuration (specified via labels).
  • --entrypoints.web.address=:80 — Where Traefik receives requests. Defines an entrypoint named web and configures it to listen on port 80, the standard port for HTTP traffic. Requests routed according to configured rules and discovered services.

The connection from Traefik to NGINX is set up by the labels on the web service. Specifically:

  • traefik.http.routers.web.rule=Host(localhost) — Tells Traefik to create a router web for HTTP traffic matching requests for the host localhost. Effectively tells Traefik to forward requests coming to localhost to this service. In production, localhost would be replaced with the actual domain name(s) that the service is intended to respond to.
  • traefik.http.services.web.loadbalancer.server.port=80 — Tells Traefik that the service web should be accessible through a load balancer on port 80. Traffic routed to this service should be forwarded to the internal port 80 of the container.

Finally you’ll note that the volume on the traefik service connects the Docker socket on the host to the corresponding socket on the container. This is to allow Traefik to automatically discover Docker services.

Start the services with docker-compose up and then go to http://localhost/ in a browser to see the HTML page.

It really could not be simpler than that! But now try http://127.0.0.1. This will give a 404. That’s because of the following rule that only routes requests to localhost:

traefik.http.routers.web.rule=Host('localhost')

Dashboard

Traefik provides an interactive dashboard that can be found at http://127.0.0.1:8080/. 📌 You can also access it at http://locahost:8080/.

The dashboard is very useful for understanding what Traefik is doing and keeping track of how it dynamically scales services in response to infrastructure changes.

Python Server on Localhost

Let’s update the previous setup to also share the page at http://127.0.0.1.

version: '3'

services:
  web:
    image: python:alpine
    container_name: web
    command: /bin/sh -c "cd /usr/src/app && python -m http.server 80"
    volumes:
      - ./hello.html:/usr/src/app/index.html:ro
    labels:
      - "traefik.http.routers.web.rule=Host(`localhost`, `127.0.0.1`)"
      - "traefik.http.services.web.loadbalancer.server.port=80"

  traefik:
    image: traefik:v2.5
    container_name: traefik
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

To keep things fresh I have also replaced NGINX as the web server with the simple Python HTTP server.

Check http://127.0.0.1 again. It should work now (this has nothing to do with changing to the Python HTTP server and everything to do with the traefik.http.routers.web.rule label).

Node Server on Domain Name

As a final example we’ll serve the page with a specific domain, www.example.com.

version: '3'

services:
  web:
    image: node:alpine
    container_name: web
    command: /bin/sh -c "npm install -g http-server && http-server -p 80 /usr/src/app"
    volumes:
      - ./hello.html:/usr/src/app/index.html:ro
    labels:
      - "traefik.http.routers.web.rule=Host(`www.example.com`)"
      - "traefik.http.services.web.loadbalancer.server.port=80"

  traefik:
    image: traefik:v2.5
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

Once again I have changed the service used to serve the page, now using Node rather than Python.

The above setup is equivalent to the following NGINX configuration, where server_name is used to specify the domain on which the resource is being served.

server {
    listen 80;
    server_name www.example.com;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

The site is now set up to be served at http://www.example.com/. Of course, we don’t have access to the www.example.com domain, so we’ll need to fake it. One option would be to add an entry to /etc/hosts:

127.0.0.1 www.example.com

With that you’ll be able to simply go to http://www.example.com in your browser. Alternatively, without modifying /etc/hosts we can also specify name resolution as an option to curl:

curl -v --resolve www.example.com:80:127.0.0.1 http://www.example.com/
* Added www.example.com:80:127.0.0.1 to DNS cache
* Hostname www.example.com was found in DNS cache
*   Trying 127.0.0.1:80...
* Connected to www.example.com (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: www.example.com
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: max-age=3600
< Content-Length: 115
< Content-Type: text/html; charset=UTF-8
< Date: Sat, 02 Mar 2024 07:41:07 GMT
< Etag: W/"10756632-115-2024-03-02T07:14:39.476Z"
< Last-Modified: Sat, 02 Mar 2024 07:14:39 GMT
< 
<!DOCTYPE html>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>
* Connection #0 to host www.example.com left intact

Conclusion

These three simple examples have show a selection of setups for using Traefik as a reverse proxy. Each setup has used a different approach to serving the content, including NGINX, Python and Node HTTP servers.