Directing different ports to different containers with Traefik
This post is mostly for myself: I find the Traefik documentation hard to navigate, so having figured this out in response to a question on Stack Overflow, I’m putting it here to help it stick in my head.
The question asks essentially how to perform port-based routing of requests to containers, so that a request for http://example.com
goes to one container while a request for http://example.com:9090
goes to a different container.
Creating entrypoints⌗
A default Traefik configuration will already have a listener on port 80, but if we want to accept connections on port 9090 we need to create a new listener: what Traefik calls an entrypoint. We do this using the --entrypoints.<name>.address
option. For example, --entrypoints.ep1.address=80
creates an entrypoint named ep1
on port 80, while --entrypoints.ep2.address=9090
creates an entrypoint named ep2
on port 9090. Those names are important because we’ll use them for mapping containers to the appropriate listener later on.
This gives us a Traefik configuration that looks something like:
proxy:
image: traefik:latest
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.ep1.address=:80
- --entrypoints.ep2.address=:9090
ports:
- "80:80"
- "127.0.0.1:8080:8080"
- "9090:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
We need to publish ports 80
and 9090
on the host in order to accept connections. Port 8080 is by default the Traefik dashboard; in this configuration I have it bound to localhost
because I don’t want to provide external access to the dashboard.
Routing services⌗
Now we need to configure our services so that connections on ports 80 and 9090 will get routed to the appropriate containers. We do this using the traefik.http.routers.<name>.entrypoints
label. Here’s a simple example:
app1:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app1.entrypoints=ep1
- traefik.http.routers.app1.rule=Host(`example.com`)
In the above configuration, we’re using the following labels:
traefik.http.routers.app1.entrypoints=ep1
This binds our
app1
container to theep1
entrypoint.traefik.http.routers.app1.rule=Host(`example.com`)
This matches requests with
Host: example.com
.
So in combination, these two rules say that any request on port 80 for Host: example.com
will be routed to the app1
container.
To get port 9090
routed to a second container, we add:
app2:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app2.rule=Host(`example.com`)
- traefik.http.routers.app2.entrypoints=ep2
This is the same thing, except we use entrypoint ep2
.
With everything running, we can watch the logs from docker-compose up
and see that a request on port 80:
curl -H 'host: example.com' localhost
Is serviced by app1
:
app1_1 | 172.20.0.2 - - [21/Jun/2022:02:44:11 +0000] "GET / HTTP/1.1" 200 354 "" "curl/7.76.1"
And that request on port 9090:
curl -H 'host: example.com' localhost:9090
Is serviced by app2
:
app2_1 | 172.20.0.2 - - [21/Jun/2022:02:44:39 +0000] "GET / HTTP/1.1" 200 354 "" "curl/7.76.1"
The complete docker-compose.yaml
file from this post looks like:
version: "3"
services:
proxy:
image: traefik:latest
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.ep1.address=:80
- --entrypoints.ep2.address=:9090
ports:
- "80:80"
- "8080:8080"
- "9090:9090"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
app1:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app1.rule=Host(`example.com`)
- traefik.http.routers.app1.entrypoints=ep1
app2:
image: docker.io/alpinelinux/darkhttpd:latest
labels:
- traefik.http.routers.app2.rule=Host(`example.com`)
- traefik.http.routers.app2.entrypoints=ep2