Skip to content

Your First Router

This chapter puts one real site behind Traefik: a backend service that already exists, given a name and a front door. Everything after this chapter is elaboration.

The setup

The lab's backends are two small programs that answer any request by saying who they are (their source is examples/backend.py; any real service — a wiki, a photo app, an admin page — behaves the same way from Traefik's side of the fence):

  • fern, listening on port 9001
  • otter, listening on port 9002

Yours might be a service already running on your server. The only things Traefik needs to know about it are its address and the name you want to give it.

The dynamic file

In the watched folder (dynamic/, from chapter 3), one file — examples/dynamic-routers.yml holds the lab's final version — begins as just this:

http:
  routers:
    fern:
      rule: "Host(`fern.test`)"
      entryPoints:
        - web
      service: fern
  services:
    fern:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:9001"

That is the entire instruction set: when a request arrives at the web entrypoint asking for the host fern.test, send it to the service fern, which currently means the server at 127.0.0.1 port 9001. Add otter the same way and two sites share one door.

Trying it, honestly

A reader following along has one problem the lab had too: fern.test is not a real name. It is from the reserved .test top-level domain, which can never be registered or collide with a real site. There are three honest ways to ask for it, in increasing order of permanence:

  • curl's --resolve (what the transcripts use): answer this one request as if DNS said so.
  • Your hosts file (/etc/hosts, or C:\Windows\System32\drivers\etc\hosts): add 127.0.0.1 fern.test and every program on your machine believes it.
  • Real DNS: in a real deployment the names are real — a public domain, or names your home router or Pi-hole hands out on the LAN — and this whole section is unnecessary.
curl -s --resolve fern.test:8000:127.0.0.1 \
  http://fern.test:8000/hello

Check (transcript t01): the fern backend answers, and its echo shows what Traefik told it — host-seen: fern.test:8000, and the forwarding headers x-forwarded-for, x-forwarded-host, x-forwarded-proto. Those headers are how a backend learns the visitor's real address and the name they asked for; services that build links or log visitors care about them, and Traefik sets them by default.

The informative 404

Ask for a name no router claims:

curl -s -o /dev/null -w '%{http_code}\n' \
  --resolve heron.test:8000:127.0.0.1 \
  http://heron.test:8000/hello

404 — and the body, if you fetch it, is Traefik's own plain text 404 page not found. Memorise the shape of that answer. From now on, a 404 in that exact voice means no router matched; a 404 in any other voice came from a backend, and your routing is fine. Half of all proxy debugging is knowing whose error you are reading (chapter 15).

What you have, and haven't, got

You have name-based routing: two services behind one door, distinguished by the name asked for. You have not got HTTPS (chapter 7), any say over paths (chapter 5), or any protection on either site (chapter 6). The router you wrote will also happily serve fern to the whole network the entrypoint listens on — a reverse proxy is a publisher, and chapter 14 returns to what that commits you to.