This episode unlocks Traefik's service discovery power through the Docker Provider: enabling the provider, Docker socket access, the traefik.http label structure, basic routing with Host and Path, load balancer service configuration, and how containers that start and stop are updated automatically.

This is the episode that most sets Traefik apart from conventional proxies: auto-discovery. After reading this episode, you will run docker compose up -d and Traefik will automatically know how to route the new container — no configuration editing, no reload, no restart.
The way it works is through the Docker Provider and labels. Traefik reads the Docker socket, finds containers marked with specific labels, then translates those labels into routers, services, and middlewares in real time. Episode 5 unpacks this mechanism from the start all the way to correct routing practices.
The Docker Provider is enabled in the static configuration. The key is mounting the Docker socket into the Traefik container — this is where Traefik observes the container world:
providers:
docker:
exposedByDefault: false
watch: trueThe endpoint line does not need to be written because the Docker provider's default already points to the daemon socket unix:///var/run/docker.sock. If your socket is somewhere else, add an endpoint line as needed. Two settings you must understand:
exposedByDefault: false: only containers with the traefik.enable=true label are routed. The value true (default) routes all containers — dangerous because unknown containers end up exposed.watch: true: Traefik continuously monitors Docker events. When a container starts, stops, or restarts, the configuration updates automatically.Mount the socket with the read-only flag for security:
services:
traefik:
image: traefik:v3
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config/traefik.yml:/etc/traefik/traefik.yml:roAll dynamic configuration from Docker is written as labels with the traefik. prefix. The basic pattern:
traefik.enable
traefik.http.routers.<name>.rule
traefik.http.routers.<name>.service
traefik.http.routers.<name>.middlewares
traefik.http.services.<name>.loadbalancer.server.port
traefik.http.middlewares.<name>.<type>.<options>Labels are grouped by component type, then component name, then configuration. A complete example of a backend container:
services:
app:
image: nginx:alpine
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`app.localhost`)
- traefik.http.routers.app.entrypoints=web
- traefik.http.routers.app.service=app-svc
- traefik.http.services.app-svc.loadbalancer.server.port=80The app container above produces a router named app and a service named app-svc pointing to container port 80. Accessing http://app.localhost will reach NGINX. Router and service naming is free, as long as it is consistent across labels.
Rules are the heart of routing. The most common examples:
Host(app.localhost): matches a specific host.Path(/api): matches the exact path /api.PathPrefix(/api): matches all paths starting with /api.Method(GET): matches a specific HTTP method.Rules can be combined with logical operators. The && (AND) and || (OR) operators plus parentheses for grouping will be fully covered in episode 6. The combination example below shows two routes in a single container:
services:
api:
image: myapi:latest
labels:
- traefik.enable=true
- traefik.http.routers.api-pub.rule=Host(`api.localhost`) && PathPrefix(`/v1`)
- traefik.http.routers.api-pub.entrypoints=web
- traefik.http.routers.api-pub.service=api-svc
- traefik.http.services.api-svc.loadbalancer.server.port=3000A router must declare which entrypoint it serves. If omitted, the router applies to all entrypoints — often causing unexpected conflicts. For priority between overlapping routers, Traefik calculates it automatically based on rule length; manual settings will be covered in episode 6.
A service defines the backend. In Docker, the backend automatically refers to the container in question; you just need to specify its internal port. Traefik v3 also supports health checks to remove unhealthy backends:
services:
api:
image: myapi:latest
labels:
- traefik.enable=true
- traefik.http.routers.api.rule=Host(`api.localhost`)
- traefik.http.routers.api.service=api-svc
- traefik.http.services.api-svc.loadbalancer.server.port=3000
- traefik.http.services.api-svc.loadbalancer.healthcheck.path=/health
- traefik.http.services.api-svc.loadbalancer.healthcheck.interval=10sIf the /health health check fails, Traefik removes that container from the load balancing rotation until it is healthy again. If all backends fail, Traefik returns HTTP 503. The same mechanism applies when a container is scaled — it automatically enters and leaves the rotation.
To prove its dynamic nature, run the following experiment:
docker compose up -d traefik
docker compose up -d app
curl -H "Host: app.localhost" http://localhost
docker compose scale app=2Run the commands above one by one while watching the HTTP Routers tab in the dashboard or calling the API:
curl -s http://localhost:8080/api/http/routers | head -n 30When app is scaled to 2, Traefik automatically adds the second server to the service without a restart. The JSON response from the API shows the real-time state. The curl command is the fastest way to verify that discovery works as expected.
Warning
Never mount the Docker socket into a container you do not trust. Anyone with socket access can fully control the Docker daemon. A read-only mount is the minimum, not a security guarantee.
Key takeaways:
exposedByDefault: false prevents containers without labels from being exposed.traefik.http.routers.<name>.* and traefik.http.services.<name>.* build routing.Host and PathPrefix rules are the basis of matching; they can be combined with operators.In episode 6 next we will go deeper into routers and rules — all available matchers such as HostRegexp, Method, Headers, and Query, logical operators, priority rules, and examples of complex rule combinations. After this, you can design routing for real-world scenarios.