HTTP routing with Traefik
Introduction
During local development, it’s quite common to need to run multiple HTTP services. You might have both an API and a frontend app, a WireMock service to mock data endpoints, or a database visualizer (such as phpMyAdmin or pgAdmin). In many development setups, these services are exposed on different ports, which then requires you to remember what’s on what port but can also introduce other problems (such as CORS).
A reverse proxy can dramatically simplify this setup by being the single exposed service and then routing requests to the appropriate service based on the request URL (either by path or hostname). Traefik is a modern, cloud-native reverse proxy and load balancer that makes developing and deploying multi-service applications easier. This guide will show you how to use Traefik with Docker to enhance your development environment.
In this guide, you will learn how to:
- Start Traefik with Docker
- Configure routing rules to split traffic between two containers
- Use Traefik in a containerized development environment
- Use Traefik to send requests to non-containerized workloads
Prerequisites
The following prerequisites are required to follow along with this how-to guide:
- Docker Desktop
- Node.js and yarn
- Basic of Docker
Using Traefik with Docker
One of the unique features of Traefik is its ability to be configured in many ways. When using the Docker provider, Traefik gets its configuration from other running containers using labels. Traefik will watch engine events (for container starts and stops), extract the labels, and update its configuration.
While there are many Traefik-monitored labels, the two most common will be:
traefik.http.routers.<service-name>.rule
- used to indicate the routing rule ( view all of the available rules here)traefik.http.services.<service-name>.loadbalancer.server.port
- indicates the port Traefik should forward the request to. Note that this container port does not need to be exposed on your host machine ( read about port detection here)
Let’s do a quick demo of starting Traefik and then configuring two additional containers to be accessible using different hostnames.
In order for two containers to be able to communicate with each other, they need to be on the same network. Create a network named
traefik-demo
using thedocker network create
command:$ docker network create traefik-demo
Start a Traefik container using the following command. The command exposes Traefik on port 80, mounts the Docker socket (which is used to monitor containers to update configuration), and passes the
--providers.docker
argument to configure Traefik to use the Docker provider.$ docker run -d --network=traefik-demo -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock traefik:v3.1.2 --providers.docker
Now, start a simple Nginx container and define the labels Traefik is watching for to configure the HTTP routing. Note that the Nginx container is not exposing any ports.
$ docker run -d --network=traefik-demo --label 'traefik.http.routers.nginx.rule=Host(`nginx.localhost`)' nginx
Once the container starts, open your browser to http://nginx.localhost to see the app (all Chromium-based browsers route *.localhost requests locally with no additional setup).
Start a second application that will use a different hostname.
$ docker run -d --network=traefik-demo --label 'traefik.http.routers.welcome.rule=Host(`welcome.localhost`)' docker/welcome-to-docker
Once the container starts, open your browser to http://welcome.localhost. You should see a “Welcome to Docker” website.
Using Traefik in development
Now that you’ve experienced Traefik, it’s time to try using it in a development environment. In this example, you will use a sample application that has a split frontend and backend. This app stack has the following configuration:
- All requests to /api to go to the API service
- All other requests to localhost go to the frontend client
- Since the app uses MySQL, db.localhost should provide phpMyAdmin to make it easy to access the database during development
The application can be accessed on GitHub at dockersamples/easy-http-routing-with-traefik.
In the
compose.yaml
file, Traefik is using the following configuration:services: proxy: image: traefik:v3.1.2 command: --providers.docker ports: - 80:80 volumes: - /var/run/docker.sock:/var/run/docker.sock
Note that this is essentially the same configuration as used earlier, but now in a Compose syntax.
The client service has the following configuration, which will start the container and provide it with the labels to receive requests at localhost.
services: # … client: image: nginx:alpine volumes: - "./client:/usr/share/nginx/html" labels: traefik.http.routers.client.rule: "Host(`localhost`)"
The api service has a similar configuration, but you’ll notice the routing rule has two conditions - the host must be “localhost” and the URL path must have a prefix of “/api”. Since this rule is more specific, Traefik will evaluate it first compared to the client rule.
services: # … api: build: ./dev/api volumes: - "./api:/var/www/html/api" labels: traefik.http.routers.api.rule: "Host(`localhost`) && PathPrefix(`/api`)"
And finally, the
phpmyadmin
service is configured to receive requests for the hostname “db.localhost”. The service also has environment variables defined to automatically log in, making it a little easier to get into the app.services: # … phpmyadmin: image: phpmyadmin:5.2.1 labels: traefik.http.routers.db.rule: "Host(`db.localhost`)" environment: PMA_USER: root PMA_PASSWORD: password
Before starting the stack, stop the Nginx container if it is still running.
And that’s it. Now, you only need to spin up the Compose stack with a docker compose up
and all of the services and applications will be ready for development.
Sending traffic to non-containerized workloads
In some situations, you may want to forward requests to applications not running in containers. In the following architecture diagram, the same application from before is used, but the API and React apps are now running natively on the host machine.
To accomplish this, Traefik will need to use another method to configure itself. The File provider lets you define the routing rules in a YAML document. Here is an example file:
http:
routers:
native-api:
rule: "Host(`localhost`) && PathPrefix(`/api`)"
service: native-api
native-client:
rule: "Host(`localhost`)"
service: native-client
services:
native-api:
loadBalancer:
servers:
- url: "http://host.docker.internal:3000/"
native-client:
loadBalancer:
servers:
- url: "http://host.docker.internal:5173/"
This configuration indicates that requests that for localhost/api
will be forwarded to a service named native-api
, which then forwards the request to
http://host.docker.internal:3000. The hostname host.docker.internal
is a name that Docker Desktop provides to send requests to the host machine.
With this file, the only change is to the Compose configuration for Traefik. There are specifically two things that have changed:
- The configuration file is mounted into the Traefik container (the exact destination path is up to you)
- The
command
is updated to add the file provider and point to the location of the configuration file
services:
proxy:
image: traefik:v3.1.2
command: --providers.docker --providers.file.filename=/config/traefik-config.yaml --api.insecure
ports:
- 80:80
- 8080:8080
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./dev/traefik-config.yaml:/config/traefik-config.yaml
Starting the example app
To run the example app that forwards requests from Traefik to native-running apps, use the following steps:
If you have the Compose stack still running, stop it with the following command:
$ docker compose down
Start the application using the provided
compose-native.yaml
file:$ docker compose -f compose-native.yaml up
Opening http://localhost will return a 502 Bad Gateway because the other apps aren’t running yet.
Start the API by running the following steps:
cd api yarn install yarn dev
Start the frontend by running the following steps in a new terminal (starting from the root of the project):
cd client yarn install yarn dev
Open the app at http://localhost. You should see an app that fetches a message from http://localhost/api/messages. You can also open http://db.localhost to view or adjust the available messages directly from the Mongo database. Traefik will ensure the requests are properly routed to the correct container or application.
When you’re done, run
docker compose down
to stop the containers and stop the Yarn apps by hittingctrl+c
.
Recap
Running multiple services doesn’t have to require tricky port configuration and a good memory. With tools like Traefik, it’s easy to launch the services you need and easily access them - whether they’re for the app itself (such as the frontend and backend) or for additional development tooling (such as phpMyAdmin).