Skip to content

Traefik & Docker

A Story of Labels & Containers

Docker

Attach labels to your containers and let Traefik do the rest!

This provider works with Docker (standalone) Engine.

The Quick Start Uses Docker

If you have not already read it, maybe you would like to go through the quick start guide that uses the Docker provider.

Configuration Examples

Configuring Docker & Deploying / Exposing one Service

Enabling the docker provider

providers:
  docker: {}
[providers.docker]
--providers.docker=true

Attaching labels to containers (in your docker compose file)

version: "3"
services:
  my-container:
    # ...
    labels:
      - traefik.http.routers.my-container.rule=Host(`example.com`)

Routing Configuration

When using Docker as a provider, Traefik uses container labels to retrieve its routing configuration.

See the list of labels in the dedicated routing section.

Routing Configuration with Labels

By default, Traefik watches for container level labels on a standalone Docker Engine.

When using Docker Compose, labels are specified by the directive labels from the "services" objects.

Not Only Docker

Please note that any tool like Nomad, Terraform, Ansible, etc. that is able to define a Docker container with labels can work with Traefik and the Docker provider.

Port Detection

Traefik retrieves the private IP and port of containers from the Docker API.

Port detection for private communication works as follows:

  • If a container exposes a single port, then Traefik uses this port.
  • If a container exposes multiple ports, then Traefik uses the lowest port. E.g. if 80 and 8080 are exposed, Traefik will use 80.
  • If a container does not expose any port, or the selection from multiple ports does not fit, then you must manually specify which port Traefik should use for communication by using the label traefik.http.services.<service_name>.loadbalancer.server.port (Read more on this label in the dedicated section in routing).

Host networking

When exposing containers that are configured with host networking, the IP address of the host is resolved as follows:

  • try a lookup of host.docker.internal
  • if the lookup was unsuccessful, try a lookup of host.containers.internal, (Podman equivalent of host.docker.internal)
  • if that lookup was also unsuccessful, fall back to 127.0.0.1

On Linux, for versions of Docker older than 20.10.0, for host.docker.internal to be defined, it should be provided as an extra_host to the Traefik container, using the --add-host flag. For example, to set it to the IP address of the bridge interface (docker0 by default): --add-host=host.docker.internal:172.17.0.1

IPv4 && IPv6

When using a docker stack that uses IPv6, Traefik will use the IPv4 container IP before its IPv6 counterpart. Therefore, on an IPv6 Docker stack, Traefik will use the IPv6 container IP.

Docker API Access

Traefik requires access to the docker socket to get its dynamic configuration.

You can specify which Docker API Endpoint to use with the directive endpoint.

Security Note

Accessing the Docker API without any restriction is a security concern: If Traefik is attacked, then the attacker might get access to the underlying host.

As explained in the Docker Daemon Attack Surface documentation:

Quote

[...] only trusted users should be allowed to control your Docker daemon [...]

Solutions

Expose the Docker socket over TCP or SSH, instead of the default Unix socket file. It allows different implementation levels of the AAA (Authentication, Authorization, Accounting) concepts, depending on your security assessment:

  • Authentication with Client Certificates as described in "Protect the Docker daemon socket."
  • Authorize and filter requests to restrict possible actions with the TecnativaDocker Socket Proxy.
  • Authorization with the Docker Authorization Plugin Mechanism
  • Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik.
  • Accounting at container level, by exposing the socket on a another container than Traefik's.
  • Accounting at kernel level, by enforcing kernel calls with mechanisms like SELinux, to only allows an identified set of actions for Traefik's process (or the "socket exposer" process).
  • SSH public key authentication (SSH is supported with Docker > 18.09)
  • Authentication using HTTP Basic authentication through an HTTP proxy that exposes the Docker daemon socket.
More Resources and Examples

Provider Configuration

endpoint

Required, Default="unix:///var/run/docker.sock"

See the Docker API Access section for more information.

Using the docker.sock

The docker-compose file shares the docker sock with the Traefik container

version: '3'

services:
  traefik:
     image: traefik:v3.2 # The official v3 Traefik docker image
     ports:
       - "80:80"
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock

We specify the docker.sock in traefik's configuration file.

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
     # ...
[providers.docker]
  endpoint = "unix:///var/run/docker.sock"
  # ...
--providers.docker.endpoint=unix:///var/run/docker.sock
# ...
Using SSH

Using Docker 18.09+ you can connect Traefik to daemon using SSH. We specify the SSH host and user in Traefik's configuration file. Note that if the server requires public keys for authentication, you must have them accessible for the user running Traefik.

providers:
  docker:
    endpoint: "ssh://[email protected]:2022"
     # ...
[providers.docker]
  endpoint = "ssh://[email protected]:2022"
  # ...
--providers.docker.endpoint=ssh://[email protected]:2022
# ...
Using HTTP

Using Docker Engine API you can connect Traefik to remote daemon using HTTP.

providers:
  docker:
    endpoint: "http://127.0.0.1:2375"
     # ...
[providers.docker]
  endpoint = "http://127.0.0.1:2375"
  # ...
--providers.docker.endpoint=http://127.0.0.1:2375
# ...
Using TCP

Using Docker Engine API you can connect Traefik to remote daemon using TCP.

providers:
  docker:
    endpoint: "tcp://127.0.0.1:2375"
     # ...
[providers.docker]
  endpoint = "tcp://127.0.0.1:2375"
  # ...
--providers.docker.endpoint=tcp://127.0.0.1:2375
# ...
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
[providers.docker]
  endpoint = "unix:///var/run/docker.sock"
--providers.docker.endpoint=unix:///var/run/docker.sock

username

Optional, Default=""

Defines the username for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.

providers:
  docker:
    username: foo
    # ...
[providers.docker]
  username = "foo"
  # ...
--providers.docker.username="foo"
# ...

password

Optional, Default=""

Defines the password for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.

providers:
  docker:
    password: foo
    # ...
[providers.docker]
  password = "foo"
  # ...
--providers.docker.password="foo"
# ...

useBindPortIP

Optional, Default=false

Traefik routes requests to the IP/port of the matching container. When setting useBindPortIP=true, you tell Traefik to use the IP/Port attached to the container's binding instead of its inner network IP/Port.

When used in conjunction with the traefik.http.services.<name>.loadbalancer.server.port label (that tells Traefik to route requests to a specific port), Traefik tries to find a binding on port traefik.http.services.<name>.loadbalancer.server.port. If it cannot find such a binding, Traefik falls back on the internal network IP of the container, but still uses the traefik.http.services.<name>.loadbalancer.server.port that is set in the label.

Examples of usebindportip in different situations.
port label Container's binding Routes to
- - IntIP:IntPort
- ExtPort:IntPort IntIP:IntPort
- ExtIp:ExtPort:IntPort ExtIp:ExtPort
LblPort - IntIp:LblPort
LblPort ExtIp:ExtPort:LblPort ExtIp:ExtPort
LblPort ExtIp:ExtPort:OtherPort IntIp:LblPort
LblPort ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 ExtIp2:LblPort

In the above table:

  • ExtIp stands for "external IP found in the binding"
  • IntIp stands for "internal network container's IP",
  • ExtPort stands for "external Port found in the binding"
  • IntPort stands for "internal network container's port."
providers:
  docker:
    useBindPortIP: true
    # ...
[providers.docker]
  useBindPortIP = true
  # ...
--providers.docker.useBindPortIP=true
# ...

exposedByDefault

Optional, Default=true

Expose containers by default through Traefik. If set to false, containers that do not have a traefik.enable=true label are ignored from the resulting routing configuration.

For additional information, refer to Restrict the Scope of Service Discovery.

providers:
  docker:
    exposedByDefault: false
    # ...
[providers.docker]
  exposedByDefault = false
  # ...
--providers.docker.exposedByDefault=false
# ...

network

Optional, Default=""

Defines a default docker network to use for connections to all containers.

This option can be overridden on a per-container basis with the traefik.docker.network label.

providers:
  docker:
    network: test
    # ...
[providers.docker]
  network = "test"
  # ...
--providers.docker.network=test
# ...

defaultRule

Optional, Default=Host(`{{ normalize .Name }}`)

The defaultRule option defines what routing rule to apply to a container if no rule is defined by a label.

It must be a valid Go template, and can use sprig template functions. The container name can be accessed with the ContainerName identifier. The service name can be accessed with the Name identifier. The template has access to all the labels defined on this container with the Labels identifier.

providers:
  docker:
    defaultRule: "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
    # ...
[providers.docker]
  defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
  # ...
--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ...
Default rule and Traefik service

The exposure of the Traefik container, combined with the default rule mechanism, can lead to create a router targeting itself in a loop. In this case, to prevent an infinite loop, Traefik adds an internal middleware to refuse the request if it comes from the same router.

httpClientTimeout

Optional, Default=0

Defines the client timeout (in seconds) for HTTP connections. If its value is 0, no timeout is set.

providers:
  docker:
    httpClientTimeout: 300
    # ...
[providers.docker]
  httpClientTimeout = 300
  # ...
--providers.docker.httpClientTimeout=300
# ...

watch

Optional, Default=true

Watch Docker events.

providers:
  docker:
    watch: false
    # ...
[providers.docker]
  watch = false
  # ...
--providers.docker.watch=false
# ...

constraints

Optional, Default=""

The constraints option can be set to an expression that Traefik matches against the container labels to determine whether to create any route for that container. If none of the container labels match the expression, no route for that container is created. If the expression is empty, all detected containers are included.

The expression syntax is based on the Label("key", "value"), and LabelRegex("key", "value") functions, as well as the usual boolean logic, as shown in examples below.

Constraints Expression Examples
# Includes only containers having a label with key `a.label.name` and value `foo`
constraints = "Label(`a.label.name`, `foo`)"
# Excludes containers having any label with key `a.label.name` and value `foo`
constraints = "!Label(`a.label.name`, `value`)"
# With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
# With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
# With logical AND and OR, with precedence set by parentheses.
constraints = "Label(`a.label.name`, `valueA`) && (Label(`another.label.name`, `valueB`) || Label(`yet.another.label.name`, `valueC`))"
# Includes only containers having a label with key `a.label.name` and a value matching the `a.+` regular expression.
constraints = "LabelRegex(`a.label.name`, `a.+`)"

For additional information, refer to Restrict the Scope of Service Discovery.

providers:
  docker:
    constraints: "Label(`a.label.name`,`foo`)"
    # ...
[providers.docker]
  constraints = "Label(`a.label.name`,`foo`)"
  # ...
--providers.docker.constraints=Label(`a.label.name`,`foo`)
# ...

tls

Optional

Defines the TLS configuration used for the secure connection to Docker.

ca

Optional

ca is the path to the certificate authority used for the secure connection to Docker, it defaults to the system bundle.

providers:
  docker:
    tls:
      ca: path/to/ca.crt
[providers.docker.tls]
  ca = "path/to/ca.crt"
--providers.docker.tls.ca=path/to/ca.crt

cert

cert is the path to the public certificate used for the secure connection to Docker. When using this option, setting the key option is required.

providers:
  docker:
    tls:
      cert: path/to/foo.cert
      key: path/to/foo.key
[providers.docker.tls]
  cert = "path/to/foo.cert"
  key = "path/to/foo.key"
--providers.docker.tls.cert=path/to/foo.cert
--providers.docker.tls.key=path/to/foo.key

key

Optional

key is the path to the private key used for the secure connection Docker. When using this option, setting the cert option is required.

providers:
  docker:
    tls:
      cert: path/to/foo.cert
      key: path/to/foo.key
[providers.docker.tls]
  cert = "path/to/foo.cert"
  key = "path/to/foo.key"
--providers.docker.tls.cert=path/to/foo.cert
--providers.docker.tls.key=path/to/foo.key

insecureSkipVerify

Optional, Default=false

If insecureSkipVerify is true, the TLS connection to Docker accepts any certificate presented by the server regardless of the hostnames it covers.

providers:
  docker:
    tls:
      insecureSkipVerify: true
[providers.docker.tls]
  insecureSkipVerify = true
--providers.docker.tls.insecureSkipVerify=true

allowEmptyServices

Optional, Default=false

If the parameter is set to true, any servers load balancer defined for Docker containers is created regardless of the healthiness of the corresponding containers. It also then stays alive and responsive even at times when it becomes empty, i.e. when all its children containers become unhealthy. This results in 503 HTTP responses instead of 404 ones, in the above cases.

providers:
  docker:
    allowEmptyServices: true
[providers.docker]
  allowEmptyServices = true
--providers.docker.allowEmptyServices=true

Using Traefik OSS in Production?

If you are using Traefik at work, consider adding enterprise-grade API gateway capabilities or commercial support for Traefik OSS.

Adding API Gateway capabilities to Traefik OSS is fast and seamless. There's no rip and replace and all configurations remain intact. See it in action via this short video.