Split Docker provider

This commit is contained in:
Ludovic Fernandez 2023-05-10 15:28:05 +02:00 committed by GitHub
parent 2cebd0a083
commit 466d7461b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 2677 additions and 1190 deletions

View file

@ -66,7 +66,6 @@ providers:
docker: docker:
endpoint: "tcp://10.10.10.10:2375" endpoint: "tcp://10.10.10.10:2375"
exposedByDefault: true exposedByDefault: true
swarmMode: true
tls: tls:
ca: dockerCA ca: dockerCA
@ -86,7 +85,6 @@ providers:
docker: docker:
endpoint: "xxxx" endpoint: "xxxx"
exposedByDefault: true exposedByDefault: true
swarmMode: true
tls: tls:
ca: xxxx ca: xxxx

View file

@ -1,5 +1,5 @@
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`) - traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)

View file

@ -1,5 +1,5 @@
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- traefik.http.routers.blog.rule=(Host(`example.com`) && Path(`/blog`)) || Host(`blog.example.org`) - traefik.http.routers.blog.rule=(Host(`example.com`) && Path(`/blog`)) || Host(`blog.example.org`)

View file

@ -1,5 +1,5 @@
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`) - traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)

View file

@ -87,7 +87,7 @@ A certificate resolver requests certificates for a set of domain names inferred
!!! example "Domain from Router's Rule Example" !!! example "Domain from Router's Rule Example"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- traefik.http.routers.blog.rule=Host(`monitoring.yak-bebop.ts.net`) && Path(`/metrics`) - traefik.http.routers.blog.rule=Host(`monitoring.yak-bebop.ts.net`) && Path(`/metrics`)
@ -141,7 +141,7 @@ A certificate resolver requests certificates for a set of domain names inferred
!!! example "Domain from Router's tls.domain Example" !!! example "Domain from Router's tls.domain Example"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- traefik.http.routers.blog.rule=Path(`/metrics`) - traefik.http.routers.blog.rule=Path(`/metrics`)

View file

@ -211,7 +211,7 @@ spec:
- bar.example.org - bar.example.org
``` ```
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
## Dynamic configuration ## Dynamic configuration
labels: labels:
- "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver" - "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver"

View file

@ -14,7 +14,7 @@ The AddPrefix middleware updates the path of a request before forwarding it.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Prefixing with /foo # Prefixing with /foo
labels: labels:
- "traefik.http.middlewares.add-foo.addprefix.prefix=/foo" - "traefik.http.middlewares.add-foo.addprefix.prefix=/foo"

View file

@ -14,7 +14,7 @@ The BasicAuth middleware restricts access to your services to known users.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Declaring the user list # Declaring the user list
# #
# Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping. # Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping.
@ -88,7 +88,7 @@ The `users` option is an array of authorized users. Each user must be declared u
Please note that these keys are not hashed or encrypted in any way, and therefore is less secure than other methods. Please note that these keys are not hashed or encrypted in any way, and therefore is less secure than other methods.
You can find more information on the [Kubernetes Basic Authentication Secret Documentation](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret) You can find more information on the [Kubernetes Basic Authentication Secret Documentation](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret)
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Declaring the user list # Declaring the user list
# #
# Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping. # Note: when used in docker-compose.yml all dollar signs in the hash need to be doubled for escaping.
@ -177,7 +177,7 @@ The file content is a list of `name:hashed-password`.
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
- Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile" - "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile"
``` ```
@ -233,7 +233,7 @@ http:
You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. You can customize the realm for the authentication with the `realm` option. The default value is `traefik`.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm" - "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm"
``` ```
@ -270,7 +270,7 @@ http:
You can define a header field to store the authenticated user using the `headerField`option. You can define a header field to store the authenticated user using the `headerField`option.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User" - "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User"
``` ```
@ -309,7 +309,7 @@ http:
Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.)
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.basicauth.removeheader=true" - "traefik.http.middlewares.test-auth.basicauth.removeheader=true"
``` ```

View file

@ -18,7 +18,7 @@ This can help services avoid large amounts of data (`multipart/form-data` for ex
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Sets the maximum request body to 2MB # Sets the maximum request body to 2MB
labels: labels:
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000" - "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000"
@ -66,7 +66,7 @@ The `maxRequestBodyBytes` option configures the maximum allowed body size for th
If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a `413` (Request Entity Too Large) response. If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a `413` (Request Entity Too Large) response.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000" - "traefik.http.middlewares.limit.buffering.maxRequestBodyBytes=2000000"
``` ```
@ -105,7 +105,7 @@ _Optional, Default=1048576_
You can configure a threshold (in bytes) from which the request will be buffered on disk instead of in memory with the `memRequestBodyBytes` option. You can configure a threshold (in bytes) from which the request will be buffered on disk instead of in memory with the `memRequestBodyBytes` option.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.limit.buffering.memRequestBodyBytes=2000000" - "traefik.http.middlewares.limit.buffering.memRequestBodyBytes=2000000"
``` ```
@ -146,7 +146,7 @@ The `maxResponseBodyBytes` option configures the maximum allowed response size f
If the response exceeds the allowed size, it is not forwarded to the client. The client gets a `500` (Internal Server Error) response instead. If the response exceeds the allowed size, it is not forwarded to the client. The client gets a `500` (Internal Server Error) response instead.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.limit.buffering.maxResponseBodyBytes=2000000" - "traefik.http.middlewares.limit.buffering.maxResponseBodyBytes=2000000"
``` ```
@ -185,7 +185,7 @@ _Optional, Default=1048576_
You can configure a threshold (in bytes) from which the response will be buffered on disk instead of in memory with the `memResponseBodyBytes` option. You can configure a threshold (in bytes) from which the response will be buffered on disk instead of in memory with the `memResponseBodyBytes` option.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.limit.buffering.memResponseBodyBytes=2000000" - "traefik.http.middlewares.limit.buffering.memResponseBodyBytes=2000000"
``` ```
@ -226,7 +226,7 @@ You can have the Buffering middleware replay the request using `retryExpression`
??? example "Retries once in the case of a network error" ??? example "Retries once in the case of a network error"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.limit.buffering.retryExpression=IsNetworkError() && Attempts() < 2" - "traefik.http.middlewares.limit.buffering.retryExpression=IsNetworkError() && Attempts() < 2"
``` ```

View file

@ -17,7 +17,7 @@ It makes reusing the same groups easier.
Below is an example of a Chain containing `AllowList`, `BasicAuth`, and `RedirectScheme`. Below is an example of a Chain containing `AllowList`, `BasicAuth`, and `RedirectScheme`.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.routers.router1.service=service1" - "traefik.http.routers.router1.service=service1"
- "traefik.http.routers.router1.middlewares=secured" - "traefik.http.routers.router1.middlewares=secured"

View file

@ -30,7 +30,7 @@ To assess if your system is healthy, the circuit breaker constantly monitors the
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Latency Check # Latency Check
labels: labels:
- "traefik.http.middlewares.latency-check.circuitbreaker.expression=LatencyAtQuantileMS(50.0) > 100" - "traefik.http.middlewares.latency-check.circuitbreaker.expression=LatencyAtQuantileMS(50.0) > 100"

View file

@ -15,7 +15,7 @@ The activation of compression, and the compression method choice rely (among oth
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Enable compression # Enable compression
labels: labels:
- "traefik.http.middlewares.test-compress.compress=true" - "traefik.http.middlewares.test-compress.compress=true"
@ -82,7 +82,7 @@ Content types are compared in a case-insensitive, whitespace-ignored manner.
Note that `application/grpc` is never compressed. Note that `application/grpc` is never compressed.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream" - "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream"
``` ```
@ -125,7 +125,7 @@ _Optional, Default=1024_
Responses smaller than the specified values will not be compressed. Responses smaller than the specified values will not be compressed.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-compress.compress.minresponsebodybytes=1200" - "traefik.http.middlewares.test-compress.compress.minresponsebodybytes=1200"
``` ```

View file

@ -18,7 +18,7 @@ when it is not set by the backend.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Enable auto-detection # Enable auto-detection
labels: labels:
- "traefik.http.middlewares.autodetect.contenttype=true" - "traefik.http.middlewares.autodetect.contenttype=true"

View file

@ -14,7 +14,7 @@ The DigestAuth middleware restricts access to your services to known users.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Declaring the user list # Declaring the user list
labels: labels:
- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" - "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e"
@ -72,7 +72,7 @@ The `users` option is an array of authorized users. Each user will be declared u
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
- For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. - For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" - "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e"
``` ```
@ -132,7 +132,7 @@ The file content is a list of `name:realm:encoded-password`.
- If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`.
- Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile" - "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile"
``` ```
@ -188,7 +188,7 @@ http:
You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. You can customize the realm for the authentication with the `realm` option. The default value is `traefik`.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm" - "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm"
``` ```
@ -225,7 +225,7 @@ http:
You can customize the header field for the authenticated user using the `headerField`option. You can customize the header field for the authenticated user using the `headerField`option.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.my-auth.digestauth.headerField=X-WebAuth-User" - "traefik.http.middlewares.my-auth.digestauth.headerField=X-WebAuth-User"
``` ```
@ -264,7 +264,7 @@ http:
Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.)
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.digestauth.removeheader=true" - "traefik.http.middlewares.test-auth.digestauth.removeheader=true"
``` ```

View file

@ -18,7 +18,7 @@ The Errors middleware returns a custom page in lieu of the default, according to
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Dynamic Custom Error Page for 5XX Status Code # Dynamic Custom Error Page for 5XX Status Code
labels: labels:
- "traefik.http.middlewares.test-errors.errors.status=500-599" - "traefik.http.middlewares.test-errors.errors.status=500-599"

View file

@ -16,7 +16,7 @@ Otherwise, the response from the authentication server is returned.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Forward authentication to example.com # Forward authentication to example.com
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth" - "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth"
@ -72,7 +72,7 @@ The following request properties are provided to the forward-auth target endpoin
The `address` option defines the authentication server address. The `address` option defines the authentication server address.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth" - "traefik.http.middlewares.test-auth.forwardauth.address=https://example.com/auth"
``` ```
@ -109,7 +109,7 @@ http:
Set the `trustForwardHeader` option to `true` to trust all `X-Forwarded-*` headers. Set the `trustForwardHeader` option to `true` to trust all `X-Forwarded-*` headers.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true" - "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true"
``` ```
@ -150,7 +150,7 @@ http:
The `authResponseHeaders` option is the list of headers to copy from the authentication server response and set on The `authResponseHeaders` option is the list of headers to copy from the authentication server response and set on
forwarded request, replacing any existing conflicting headers. forwarded request, replacing any existing conflicting headers.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret"
``` ```
@ -197,7 +197,7 @@ set on forwarded request, after stripping all headers that match the regex.
It allows partial matching of the regular expression against the header key. It allows partial matching of the regular expression against the header key.
The start of string (`^`) and end of string (`$`) anchors should be used to ensure a full match against the header key. The start of string (`^`) and end of string (`$`) anchors should be used to ensure a full match against the header key.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-" - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-"
``` ```
@ -245,7 +245,7 @@ The `authRequestHeaders` option is the list of the headers to copy from the requ
It allows filtering headers that should not be passed to the authentication server. It allows filtering headers that should not be passed to the authentication server.
If not set or empty then all request headers are passed. If not set or empty then all request headers are passed.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader" - "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader"
``` ```
@ -298,7 +298,7 @@ _Optional_
`ca` is the path to the certificate authority used for the secured connection to the authentication server, `ca` is the path to the certificate authority used for the secured connection to the authentication server,
it defaults to the system bundle. it defaults to the system bundle.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" - "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt"
``` ```
@ -355,7 +355,7 @@ _Optional_
`cert` is the path to the public certificate used for the secure connection to the authentication server. `cert` is the path to the public certificate used for the secure connection to the authentication server.
When using this option, setting the `key` option is required. When using this option, setting the `key` option is required.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" - "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" - "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key"
@ -420,7 +420,7 @@ _Optional_
`key` is the path to the private key used for the secure connection to the authentication server. `key` is the path to the private key used for the secure connection to the authentication server.
When using this option, setting the `cert` option is required. When using this option, setting the `cert` option is required.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" - "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" - "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key"
@ -484,7 +484,7 @@ _Optional, Default=false_
If `insecureSkipVerify` is `true`, the TLS connection to the authentication server accepts any certificate presented by the server regardless of the hostnames it covers. If `insecureSkipVerify` is `true`, the TLS connection to the authentication server accepts any certificate presented by the server regardless of the hostnames it covers.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true" - "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true"
``` ```

View file

@ -17,7 +17,7 @@ The GrpcWeb middleware converts gRPC Web requests to HTTP/2 gRPC requests before
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-grpcweb.grpcweb.allowOrigins=*" - "traefik.http.middlewares.test-grpcweb.grpcweb.allowOrigins=*"
``` ```

View file

@ -20,7 +20,7 @@ A set of forwarded headers are automatically added by default. See the [FAQ](../
The following example adds the `X-Script-Name` header to the proxied request and the `X-Custom-Response-Header` header to the response The following example adds the `X-Script-Name` header to the proxied request and the `X-Custom-Response-Header` header to the response
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Script-Name=test" - "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Script-Name=test"
- "traefik.http.middlewares.testHeader.headers.customresponseheaders.X-Custom-Response-Header=value" - "traefik.http.middlewares.testHeader.headers.customresponseheaders.X-Custom-Response-Header=value"
@ -69,7 +69,7 @@ http:
In the following example, requests are proxied with an extra `X-Script-Name` header while their `X-Custom-Request-Header` header gets stripped, In the following example, requests are proxied with an extra `X-Script-Name` header while their `X-Custom-Request-Header` header gets stripped,
and responses are stripped of their `X-Custom-Response-Header` header. and responses are stripped of their `X-Custom-Response-Header` header.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test" - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test"
- "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Custom-Request-Header=" - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Custom-Request-Header="
@ -123,7 +123,7 @@ http:
Security-related headers (HSTS headers, Browser XSS filter, etc) can be managed similarly to custom headers as shown above. Security-related headers (HSTS headers, Browser XSS filter, etc) can be managed similarly to custom headers as shown above.
This functionality makes it possible to easily use security features by adding headers. This functionality makes it possible to easily use security features by adding headers.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.testHeader.headers.framedeny=true" - "traefik.http.middlewares.testHeader.headers.framedeny=true"
- "traefik.http.middlewares.testHeader.headers.browserxssfilter=true" - "traefik.http.middlewares.testHeader.headers.browserxssfilter=true"
@ -170,7 +170,7 @@ instead the response will be generated and sent back to the client directly.
Please note that the example below is by no means authoritative or exhaustive, Please note that the example below is by no means authoritative or exhaustive,
and should not be used as is for production. and should not be used as is for production.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT" - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
- "traefik.http.middlewares.testheader.headers.accesscontrolallowheaders=*" - "traefik.http.middlewares.testheader.headers.accesscontrolallowheaders=*"

View file

@ -14,7 +14,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10" - "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
``` ```
@ -57,7 +57,7 @@ http:
The `amount` option defines the maximum amount of allowed simultaneous in-flight request. The `amount` option defines the maximum amount of allowed simultaneous in-flight request.
The middleware responds with `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `sourceCriterion` strategy). The middleware responds with `HTTP 429 Too Many Requests` if there are already `amount` requests in progress (based on the same `sourceCriterion` strategy).
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10" - "traefik.http.middlewares.test-inflightreq.inflightreq.amount=10"
``` ```
@ -122,7 +122,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.depth=2" - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.depth=2"
``` ```
@ -176,7 +176,7 @@ http:
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | | `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
``` ```
@ -222,7 +222,7 @@ http:
Name of the header used to group incoming requests. Name of the header used to group incoming requests.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username" - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
``` ```
@ -262,7 +262,7 @@ http:
Whether to consider the request host as the source. Whether to consider the request host as the source.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true" - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
``` ```

View file

@ -12,7 +12,7 @@ IPAllowList accepts / refuses requests based on the client IP.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Accepts request from defined IP # Accepts request from defined IP
labels: labels:
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
@ -83,7 +83,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and take th
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Allowlisting Based on `X-Forwarded-For` with `depth=2` # Allowlisting Based on `X-Forwarded-For` with `depth=2`
labels: labels:
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" - "traefik.http.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
@ -149,7 +149,7 @@ http:
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | | `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Exclude from `X-Forwarded-For` # Exclude from `X-Forwarded-For`
labels: labels:
- "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"

View file

@ -12,7 +12,7 @@ Controlling connections
## Configuration Example ## Configuration Example
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# As a Docker Label # As a Docker Label
whoami: whoami:
# A container that exposes an API to show its IP address # A container that exposes an API to show its IP address

View file

@ -18,7 +18,7 @@ PassTLSClientCert adds the selected data from the passed client TLS certificate
Pass the pem in the `X-Forwarded-Tls-Client-Cert` header. Pass the pem in the `X-Forwarded-Tls-Client-Cert` header.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Pass the pem in the `X-Forwarded-Tls-Client-Cert` header. # Pass the pem in the `X-Forwarded-Tls-Client-Cert` header.
labels: labels:
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.pem=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.pem=true"
@ -57,7 +57,7 @@ http:
??? example "Pass the pem in the `X-Forwarded-Tls-Client-Cert` header" ??? example "Pass the pem in the `X-Forwarded-Tls-Client-Cert` header"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Pass all the available info in the `X-Forwarded-Tls-Client-Cert-Info` header # Pass all the available info in the `X-Forwarded-Tls-Client-Cert-Info` header
labels: labels:
- "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notafter=true" - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notafter=true"

View file

@ -14,7 +14,7 @@ It is based on a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) impl
## Configuration Example ## Configuration Example
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Here, an average of 100 requests per second is allowed. # Here, an average of 100 requests per second is allowed.
# In addition, a burst of 50 requests is allowed. # In addition, a burst of 50 requests is allowed.
labels: labels:
@ -73,7 +73,7 @@ It defaults to `0`, which means no rate limiting.
The rate is actually defined by dividing `average` by `period`. The rate is actually defined by dividing `average` by `period`.
So for a rate below 1 req/s, one needs to define a `period` larger than a second. So for a rate below 1 req/s, one needs to define a `period` larger than a second.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# 100 reqs/s # 100 reqs/s
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=100" - "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
@ -121,7 +121,7 @@ r = average / period
It defaults to `1` second. It defaults to `1` second.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# 6 reqs/minute # 6 reqs/minute
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.average=6" - "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
@ -170,7 +170,7 @@ http:
It defaults to `1`. It defaults to `1`.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100" - "traefik.http.middlewares.test-ratelimit.ratelimit.burst=100"
``` ```
@ -232,7 +232,7 @@ The `depth` option tells Traefik to use the `X-Forwarded-For` header and select
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` |
| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | | `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.depth=2" - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.depth=2"
``` ```
@ -313,7 +313,7 @@ and the first IP that is _not_ in the pool (if any) is returned.
| `"10.0.0.1,11.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | | `"10.0.0.1,11.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` |
| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | | `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` |
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7" - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
``` ```
@ -359,7 +359,7 @@ http:
Name of the header used to group incoming requests. Name of the header used to group incoming requests.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username" - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
``` ```
@ -399,7 +399,7 @@ http:
Whether to consider the request host as the source. Whether to consider the request host as the source.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true" - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
``` ```

View file

@ -16,7 +16,7 @@ The RedirectRegex redirects a request using regex matching and replacement.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Redirect with domain replacement # Redirect with domain replacement
# Note: all dollar signs need to be doubled for escaping. # Note: all dollar signs need to be doubled for escaping.
labels: labels:

View file

@ -25,7 +25,7 @@ The RedirectScheme middleware redirects the request if the request scheme is dif
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Redirect to https # Redirect to https
labels: labels:
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https" - "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
@ -75,7 +75,7 @@ http:
Set the `permanent` option to `true` to apply a permanent redirection. Set the `permanent` option to `true` to apply a permanent redirection.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Redirect to https # Redirect to https
labels: labels:
# ... # ...
@ -123,7 +123,7 @@ http:
The `scheme` option defines the scheme of the new URL. The `scheme` option defines the scheme of the new URL.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Redirect to https # Redirect to https
labels: labels:
- "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https" - "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
@ -166,7 +166,7 @@ http:
The `port` option defines the port of the new URL. The `port` option defines the port of the new URL.
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Redirect to https # Redirect to https
labels: labels:
# ... # ...

View file

@ -16,7 +16,7 @@ Replace the path of the request URL.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Replace the path with /foo # Replace the path with /foo
labels: labels:
- "traefik.http.middlewares.test-replacepath.replacepath.path=/foo" - "traefik.http.middlewares.test-replacepath.replacepath.path=/foo"

View file

@ -16,7 +16,7 @@ The ReplaceRegex replaces the path of a URL using regex matching and replacement
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Replace path with regex # Replace path with regex
labels: labels:
- "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)" - "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)"

View file

@ -18,7 +18,7 @@ The Retry middleware has an optional configuration to enable an exponential back
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Retry 4 times with exponential backoff # Retry 4 times with exponential backoff
labels: labels:
- "traefik.http.middlewares.test-retry.retry.attempts=4" - "traefik.http.middlewares.test-retry.retry.attempts=4"

View file

@ -16,7 +16,7 @@ Remove the specified prefixes from the URL path.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Strip prefix /foobar and /fiibar # Strip prefix /foobar and /fiibar
labels: labels:
- "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar" - "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar"

View file

@ -12,7 +12,7 @@ Remove the matching prefixes from the URL path.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/" - "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/"
``` ```

View file

@ -23,7 +23,7 @@ Middlewares that use the same protocol can be combined into chains to fit every
## Configuration Example ## Configuration Example
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# As a Docker Label # As a Docker Label
whoami: whoami:
# A container that exposes an API to show its IP address # A container that exposes an API to show its IP address

View file

@ -7,7 +7,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.tcp.middlewares.test-inflightconn.inflightconn.amount=10" - "traefik.tcp.middlewares.test-inflightconn.inflightconn.amount=10"
``` ```

View file

@ -12,7 +12,7 @@ IPAllowList accepts / refuses connections based on the client IP.
## Configuration Examples ## Configuration Examples
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Accepts connections from defined IP # Accepts connections from defined IP
labels: labels:
- "traefik.tcp.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7" - "traefik.tcp.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"

View file

@ -12,7 +12,7 @@ Controlling connections
## Configuration Example ## Configuration Example
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# As a Docker Label # As a Docker Label
whoami: whoami:
# A container that exposes an API to show its IP address # A container that exposes an API to show its IP address

View file

@ -38,7 +38,7 @@ Then any router can refer to an instance of the wanted middleware.
!!! info "v1" !!! info "v1"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test" - "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test"
- "traefik.frontend.auth.basic.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" - "traefik.frontend.auth.basic.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
@ -100,7 +100,7 @@ Then any router can refer to an instance of the wanted middleware.
!!! info "v2" !!! info "v2"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.routers.router0.rule=Host(`test.localhost`) && PathPrefix(`/test`)" - "traefik.http.routers.router0.rule=Host(`test.localhost`) && PathPrefix(`/test`)"
- "traefik.http.routers.router0.middlewares=auth" - "traefik.http.routers.router0.middlewares=auth"
@ -317,7 +317,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o
namespace: default namespace: default
``` ```
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
# myTLSOptions must be defined by another provider, in this instance in the File Provider. # myTLSOptions must be defined by another provider, in this instance in the File Provider.
# see the cross provider section # see the cross provider section
@ -428,7 +428,7 @@ To apply a redirection:
!!! info "v2" !!! info "v2"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
traefik.http.routers.app.rule: Host(`example.net`) traefik.http.routers.app.rule: Host(`example.net`)
traefik.http.routers.app.entrypoints: web traefik.http.routers.app.entrypoints: web
@ -556,7 +556,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
!!! info "v1" !!! info "v1"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin" - "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin"
``` ```
@ -588,7 +588,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
!!! info "v2" !!! info "v2"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
labels: labels:
- "traefik.http.routers.admin.rule=Host(`example.org`) && PathPrefix(`/admin`)" - "traefik.http.routers.admin.rule=Host(`example.org`) && PathPrefix(`/admin`)"
- "traefik.http.routers.admin.middlewares=admin-stripprefix" - "traefik.http.routers.admin.middlewares=admin-stripprefix"
@ -1044,7 +1044,7 @@ To activate the dashboard, you can either:
!!! info "v2" !!! info "v2"
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# dynamic configuration # dynamic configuration
labels: labels:
- "traefik.http.routers.api.rule=Host(`traefik.docker.localhost`)" - "traefik.http.routers.api.rule=Host(`traefik.docker.localhost`)"

View file

@ -87,3 +87,10 @@ In v3, the InfluxDB v1 metrics provider has been removed because InfluxDB v1.x m
In v3 the Kubernetes CRDs API Group `traefik.containo.us` has been removed. In v3 the Kubernetes CRDs API Group `traefik.containo.us` has been removed.
Please use the API Group `traefik.io` instead. Please use the API Group `traefik.io` instead.
## Docker & Docker Swarm
In v3, the provider Docker has been split into 2 providers:
- Docker provider (without Swarm support)
- Swarm provider (Swarm support only)

View file

@ -1,4 +1,4 @@
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Dynamic Configuration # Dynamic Configuration
labels: labels:
- "traefik.http.routers.api.rule=Host(`traefik.example.com`)" - "traefik.http.routers.api.rule=Host(`traefik.example.com`)"

View file

@ -1,4 +1,4 @@
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
# Dynamic Configuration # Dynamic Configuration
labels: labels:
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"

View file

@ -12,8 +12,7 @@ A Story of Labels & Containers
Attach labels to your containers and let Traefik do the rest! Attach labels to your containers and let Traefik do the rest!
Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/) This provider works with [Docker (standalone) Engine](https://docs.docker.com/engine/).
and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
!!! tip "The Quick Start Uses Docker" !!! tip "The Quick Start Uses Docker"
@ -49,49 +48,6 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
- traefik.http.routers.my-container.rule=Host(`example.com`) - traefik.http.routers.my-container.rule=Host(`example.com`)
``` ```
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
Enabling the docker provider (Swarm Mode)
```yaml tab="File (YAML)"
providers:
docker:
# swarm classic (1.12-)
# endpoint: "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint: "tcp://127.0.0.1:2377"
swarmMode: true
```
```toml tab="File (TOML)"
[providers.docker]
# swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint = "tcp://127.0.0.1:2377"
swarmMode = true
```
```bash tab="CLI"
# swarm classic (1.12-)
# --providers.docker.endpoint=tcp://127.0.0.1:2375
# docker swarm mode (1.12+)
--providers.docker.endpoint=tcp://127.0.0.1:2377
--providers.docker.swarmMode=true
```
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
```yaml
version: "3"
services:
my-container:
deploy:
labels:
- traefik.http.routers.my-container.rule=Host(`example.com`)
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
```
## Routing Configuration ## Routing Configuration
When using Docker as a [provider](./overview.md), When using Docker as a [provider](./overview.md),
@ -124,14 +80,13 @@ Port detection works as follows:
- If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports, - If a container [exposes](https://docs.docker.com/engine/reference/builder/#expose) multiple ports,
or does not expose any port, then you must manually specify which port Traefik should use for communication or does not expose any port, then you must manually specify which port Traefik should use for communication
by using the label `traefik.http.services.<service_name>.loadbalancer.server.port` by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
(Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#port)). (Read more on this label in the dedicated section in [routing](../routing/providers/docker.md#services)).
### Host networking ### Host networking
When exposing containers that are configured with [host networking](https://docs.docker.com/network/host/), When exposing containers that are configured with [host networking](https://docs.docker.com/network/host/),
the IP address of the host is resolved as follows: the IP address of the host is resolved as follows:
<!-- TODO: verify and document the swarm mode case with container.Node.IPAddress coming from the API -->
- try a lookup of `host.docker.internal` - try a lookup of `host.docker.internal`
- if the lookup was unsuccessful, try a lookup of `host.containers.internal`, ([Podman](https://docs.podman.io/en/latest/) equivalent of `host.docker.internal`) - if the lookup was unsuccessful, try a lookup of `host.containers.internal`, ([Podman](https://docs.podman.io/en/latest/) equivalent of `host.docker.internal`)
- if that lookup was also unsuccessful, fall back to `127.0.0.1` - if that lookup was also unsuccessful, fall back to `127.0.0.1`
@ -175,7 +130,6 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
- Authorization with the [Docker Authorization Plugin Mechanism](https://web.archive.org/web/20190920092526/https://docs.docker.com/engine/extend/plugins_authorization/) - Authorization with the [Docker Authorization Plugin Mechanism](https://web.archive.org/web/20190920092526/https://docs.docker.com/engine/extend/plugins_authorization/)
- Accounting at networking level, by exposing the socket only inside a Docker private network, only available for Traefik. - 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 container level, by exposing the socket on a another container than Traefik's.
With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), 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) - SSH public key authentication (SSH is supported with Docker > 18.09)
@ -192,69 +146,13 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`]
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/) - [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy) - [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
## Docker Swarm Mode
To enable Docker Swarm (instead of standalone Docker) as a configuration provider,
set the [`swarmMode`](#swarmmode) directive to `true`.
### Routing Configuration with Labels
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/)).
### Port Detection
Docker Swarm does not provide any [port detection](#port-detection) information to Traefik.
Therefore, you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
(Check the reference for this label in the [routing section for Docker](../routing/providers/docker.md#port)).
### Docker API Access
Docker Swarm Mode follows the same rules as Docker [API Access](#docker-api-access).
Since the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes),
these are the nodes that Traefik should be scheduled on by deploying Traefik with a constraint on the node "role":
```shell tab="With Docker CLI"
docker service create \
--constraint=node.role==manager \
#... \
```
```yml tab="With Docker Compose"
version: '3'
services:
traefik:
# ...
deploy:
placement:
constraints:
- node.role == manager
```
!!! tip "Scheduling Traefik on Worker Nodes"
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
socket is reachable.
Please consider the security implications by reading the [Security Note](#security-note).
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
## Provider Configuration ## Provider Configuration
### `endpoint` ### `endpoint`
_Required, Default="unix:///var/run/docker.sock"_ _Required, Default="unix:///var/run/docker.sock"_
See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API Access](#docker-api-access_1) for more information. See the [Docker API Access](#docker-api-access) section for more information.
??? example "Using the docker.sock" ??? example "Using the docker.sock"
@ -464,54 +362,6 @@ providers:
# ... # ...
``` ```
### `swarmMode`
_Optional, Default=false_
Enables the Swarm Mode (instead of standalone Docker).
```yaml tab="File (YAML)"
providers:
docker:
swarmMode: true
# ...
```
```toml tab="File (TOML)"
[providers.docker]
swarmMode = true
# ...
```
```bash tab="CLI"
--providers.docker.swarmMode=true
# ...
```
### `swarmModeRefreshSeconds`
_Optional, Default=15_
Defines the polling interval (in seconds) for Swarm Mode.
```yaml tab="File (YAML)"
providers:
docker:
swarmModeRefreshSeconds: 30
# ...
```
```toml tab="File (TOML)"
[providers.docker]
swarmModeRefreshSeconds = 30
# ...
```
```bash tab="CLI"
--providers.docker.swarmModeRefreshSeconds=30
# ...
```
### `httpClientTimeout` ### `httpClientTimeout`
_Optional, Default=0_ _Optional, Default=0_

View file

@ -72,7 +72,7 @@ For the list of the providers names, see the [supported providers](#supported-pr
Using the add-foo-prefix middleware from other providers: Using the add-foo-prefix middleware from other providers:
```yaml tab="Docker" ```yaml tab="Docker & Swarm"
your-container: # your-container: #
image: your-docker-image image: your-docker-image

View file

@ -0,0 +1,697 @@
---
title: "Traefik Docker Swarm Documentation"
description: "Learn how to achieve configuration discovery in Traefik through Docker Swarm. Read the technical documentation."
---
# Traefik & Docker Swarm
A Story of Labels & Containers
{: .subtitle }
![Docker](../assets/img/providers/docker.png)
Attach labels to your containers and let Traefik do the rest!
This provider works with [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
!!! tip "The Quick Start Uses Docker"
If you have not already read it, maybe you would like to go through the [quick start guide](../getting-started/quick-start.md) that uses the Docker provider.
## Configuration Examples
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
Enabling the Swarm provider
```yaml tab="File (YAML)"
providers:
swarm:
# swarm classic (1.12-)
# endpoint: "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint: "tcp://127.0.0.1:2377"
```
```toml tab="File (TOML)"
[providers.swarm]
# swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint = "tcp://127.0.0.1:2377"
```
```bash tab="CLI"
# swarm classic (1.12-)
# --providers.swarm.endpoint=tcp://127.0.0.1:2375
# docker swarm mode (1.12+)
--providers.swarm.endpoint=tcp://127.0.0.1:2377
```
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
```yaml
version: "3"
services:
my-container:
deploy:
labels:
- traefik.http.routers.my-container.rule=Host(`example.com`)
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
```
## Routing Configuration
When using Docker as a [provider](./overview.md),
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#label) to retrieve its routing configuration.
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
### Routing Configuration with Labels
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
When using Docker Compose, labels are specified by the directive
[`labels`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels) from the
["services" objects](https://docs.docker.com/compose/compose-file/compose-file-v3/#service-configuration-reference).
!!! tip "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 Swarm provider.
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/)).
### Port Detection
Traefik retrieves the private IP and port of containers from the Docker API.
Docker Swarm does not provide any port detection information to Traefik.
Therefore, you **must** specify the port to use for communication by using the label `traefik.http.services.<service_name>.loadbalancer.server.port`
(Check the reference for this label in the [routing section for Swarm](../routing/providers/swarm.md#services)).
### Host networking
When exposing containers that are configured with [host networking](https://docs.docker.com/network/host/),
the IP address of the host is resolved as follows:
<!-- TODO: verify and document the swarm mode case with container.Node.IPAddress coming from the API -->
- try a lookup of `host.docker.internal`
- if the lookup was unsuccessful, try a lookup of `host.containers.internal`, ([Podman](https://docs.podman.io/en/latest/) 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`](#endpoint).
!!! warning "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.
{: #security-note }
As explained in the [Docker Daemon Attack Surface documentation](https://docs.docker.com/engine/security/#docker-daemon-attack-surface):
!!! quote
[...] only **trusted** users should be allowed to control your Docker daemon [...]
??? success "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](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment:
- Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/protect-access/)
- Authorize and filter requests to restrict possible actions with [the TecnativaDocker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy).
- Authorization with the [Docker Authorization Plugin Mechanism](https://web.archive.org/web/20190920092526/https://docs.docker.com/engine/extend/plugins_authorization/)
- 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.
It allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes.
- Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), 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)
??? info "More Resources and Examples"
- ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c)
- [Traefik and Docker: A Discussion with Docker Captain, Bret Fisher](https://blog.traefik.io/traefik-and-docker-a-discussion-with-docker-captain-bret-fisher-7f0b9a54ff88)
- [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY)
- [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/)
- [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623)
- [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html)
- [Traefik issue GH-4174 about security with Docker socket](https://github.com/traefik/traefik/issues/4174)
- [Inspecting Docker Activity with Socat](https://developers.redhat.com/blog/2015/02/25/inspecting-docker-activity-with-socat/)
- [Letting Traefik run on Worker Nodes](https://blog.mikesir87.io/2018/07/letting-traefik-run-on-worker-nodes/)
- [Docker Socket Proxy from Tecnativa](https://github.com/Tecnativa/docker-socket-proxy)
Since the Swarm API is only exposed on the [manager nodes](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes),
these are the nodes that Traefik should be scheduled on by deploying Traefik with a constraint on the node "role":
```shell tab="With Docker CLI"
docker service create \
--constraint=node.role==manager \
#... \
```
```yml tab="With Docker Compose"
version: '3'
services:
traefik:
# ...
deploy:
placement:
constraints:
- node.role == manager
```
!!! tip "Scheduling Traefik on Worker Nodes"
Following the guidelines given in the previous section ["Docker API Access"](#docker-api-access),
if you expose the Docker API through TCP, then Traefik can be scheduled on any node if the TCP
socket is reachable.
Please consider the security implications by reading the [Security Note](#security-note).
A good example can be found on [Bret Fisher's repository](https://github.com/BretFisher/dogvscat/blob/master/stack-proxy-global.yml#L124).
### `endpoint`
_Required, Default="unix:///var/run/docker.sock"_
See the [Docker Swarm API Access](#docker-api-access) section for more information.
??? example "Using the docker.sock"
The docker-compose file shares the docker sock with the Traefik container
```yaml
version: '3'
services:
traefik:
image: traefik:v3.0 # The official v2 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.
```yaml tab="File (YAML)"
providers:
swarm:
endpoint: "unix:///var/run/docker.sock"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
endpoint = "unix:///var/run/docker.sock"
# ...
```
```bash tab="CLI"
--providers.swarm.endpoint=unix:///var/run/docker.sock
# ...
```
??? example "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 is server requires public keys for authentication you must have those accessible for user who runs Traefik.
```yaml tab="File (YAML)"
providers:
docker:
endpoint: "ssh://traefik@192.168.2.5:2022"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
endpoint = "ssh://traefik@192.168.2.5:2022"
# ...
```
```bash tab="CLI"
--providers.swarm.endpoint=ssh://traefik@192.168.2.5:2022
# ...
```
```yaml tab="File (YAML)"
providers:
swarm:
endpoint: "unix:///var/run/docker.sock"
```
```toml tab="File (TOML)"
[providers.swarm]
endpoint = "unix:///var/run/docker.sock"
```
```bash tab="CLI"
--providers.swarm.endpoint=unix:///var/run/docker.sock
```
### `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.
??? example "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 |
!!! info ""
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."
```yaml tab="File (YAML)"
providers:
swarm:
useBindPortIP: true
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
useBindPortIP = true
# ...
```
```bash tab="CLI"
--providers.swarm.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](./overview.md#restrict-the-scope-of-service-discovery).
```yaml tab="File (YAML)"
providers:
swarm:
exposedByDefault: false
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
exposedByDefault = false
# ...
```
```bash tab="CLI"
--providers.swarm.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.
```yaml tab="File (YAML)"
providers:
swarm:
network: test
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
network = "test"
# ...
```
```bash tab="CLI"
--providers.swarm.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](https://pkg.go.dev/text/template/), and can use
[sprig template functions](https://masterminds.github.io/sprig/).
The container service name can be accessed with the `Name` identifier,
and the template has access to all the labels defined on this container.
```yaml tab="File (YAML)"
providers:
swarm:
defaultRule: "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
defaultRule = "Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
# ...
```
```bash tab="CLI"
--providers.swarm.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ...
```
### `swarmMode`
_Optional, Default=false_
Enables the Swarm Mode (instead of standalone Docker).
```yaml tab="File (YAML)"
providers:
swarm:
swarmMode: true
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
swarmMode = true
# ...
```
```bash tab="CLI"
--providers.swarm.swarmMode=true
# ...
```
### `swarmModeRefreshSeconds`
_Optional, Default=15_
Defines the polling interval (in seconds) for Swarm Mode.
```yaml tab="File (YAML)"
providers:
swarm:
swarmModeRefreshSeconds: 30
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
swarmModeRefreshSeconds = 30
# ...
```
```bash tab="CLI"
--providers.swarm.swarmModeRefreshSeconds=30
# ...
```
### `httpClientTimeout`
_Optional, Default=0_
Defines the client timeout (in seconds) for HTTP connections. If its value is `0`, no timeout is set.
```yaml tab="File (YAML)"
providers:
swarm:
httpClientTimeout: 300
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
httpClientTimeout = 300
# ...
```
```bash tab="CLI"
--providers.swarm.httpClientTimeout=300
# ...
```
### `watch`
_Optional, Default=true_
Watch Docker events.
```yaml tab="File (YAML)"
providers:
swarm:
watch: false
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
watch = false
# ...
```
```bash tab="CLI"
--providers.swarm.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.
??? example "Constraints Expression Examples"
```toml
# Includes only containers having a label with key `a.label.name` and value `foo`
constraints = "Label(`a.label.name`, `foo`)"
```
```toml
# Excludes containers having any label with key `a.label.name` and value `foo`
constraints = "!Label(`a.label.name`, `value`)"
```
```toml
# With logical AND.
constraints = "Label(`a.label.name`, `valueA`) && Label(`another.label.name`, `valueB`)"
```
```toml
# With logical OR.
constraints = "Label(`a.label.name`, `valueA`) || Label(`another.label.name`, `valueB`)"
```
```toml
# 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`))"
```
```toml
# 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](./overview.md#restrict-the-scope-of-service-discovery).
```yaml tab="File (YAML)"
providers:
swarm:
constraints: "Label(`a.label.name`,`foo`)"
# ...
```
```toml tab="File (TOML)"
[providers.swarm]
constraints = "Label(`a.label.name`,`foo`)"
# ...
```
```bash tab="CLI"
--providers.swarm.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.
```yaml tab="File (YAML)"
providers:
swarm:
tls:
ca: path/to/ca.crt
```
```toml tab="File (TOML)"
[providers.swarm.tls]
ca = "path/to/ca.crt"
```
```bash tab="CLI"
--providers.swarm.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.
```yaml tab="File (YAML)"
providers:
swarm:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[providers.swarm.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--providers.swarm.tls.cert=path/to/foo.cert
--providers.swarm.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.
```yaml tab="File (YAML)"
providers:
swarm:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[providers.swarm.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--providers.swarm.tls.cert=path/to/foo.cert
--providers.swarm.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.
```yaml tab="File (YAML)"
providers:
swarm:
tls:
insecureSkipVerify: true
```
```toml tab="File (TOML)"
[providers.swarm.tls]
insecureSkipVerify = true
```
```bash tab="CLI"
--providers.swarm.tls.insecureSkipVerify=true
```
### `allowEmptyServices`
_Optional, Default=false_
If the parameter is set to `true`,
any [servers load balancer](../routing/services/index.md#servers-load-balancer) defined for Docker containers is created
regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) 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.
```yaml tab="File (YAML)"
providers:
swarm:
allowEmptyServices: true
```
```toml tab="File (TOML)"
[providers.swarm]
allowEmptyServices = true
```
```bash tab="CLI"
--providers.swarm.allowEmptyServices=true
```
{!traefik-for-business-applications.md!}

View file

@ -8,7 +8,7 @@ description: "Reference dynamic configuration with Docker labels in Traefik Prox
Dynamic configuration with Docker Labels Dynamic configuration with Docker Labels
{: .subtitle } {: .subtitle }
The labels are case insensitive. The labels are case-insensitive.
```yaml ```yaml
labels: labels:

View file

@ -1,3 +1,2 @@
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=foobar" - "traefik.docker.network=foobar"
- "traefik.docker.lbswarm=true"

View file

@ -0,0 +1,17 @@
---
title: "Traefik Docker Swarm Configuration Documentation"
description: "Reference dynamic configuration with Docker Swarm labels in Traefik Proxy. Read the technical documentation."
---
# Docker Swarm Configuration Reference
Dynamic configuration with Docker Labels
{: .subtitle }
The labels are case-insensitive.
```yaml
labels:
--8<-- "content/reference/dynamic-configuration/swarm.yml"
--8<-- "content/reference/dynamic-configuration/docker-labels.yml"
```

View file

@ -0,0 +1,3 @@
- "traefik.enable=true"
- "traefik.docker.network=foobar"
- "traefik.docker.lbswarm=true"

View file

@ -532,7 +532,7 @@ Constraints is an expression that Traefik matches against the container's labels
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
`--providers.docker.endpoint`: `--providers.docker.endpoint`:
Docker server endpoint. Can be a tcp or a unix socket endpoint. (Default: ```unix:///var/run/docker.sock```) Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
`--providers.docker.exposedbydefault`: `--providers.docker.exposedbydefault`:
Expose containers by default. (Default: ```true```) Expose containers by default. (Default: ```true```)
@ -543,12 +543,6 @@ Client timeout for HTTP connections. (Default: ```0```)
`--providers.docker.network`: `--providers.docker.network`:
Default Docker network used. Default Docker network used.
`--providers.docker.swarmmode`:
Use Docker on Swarm Mode. (Default: ```false```)
`--providers.docker.swarmmoderefreshseconds`:
Polling interval for swarm mode. (Default: ```15```)
`--providers.docker.tls.ca`: `--providers.docker.tls.ca`:
TLS CA TLS CA
@ -855,6 +849,51 @@ Enable Rest backend with default settings. (Default: ```false```)
`--providers.rest.insecure`: `--providers.rest.insecure`:
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```) Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
`--providers.swarm`:
Enable Docker Swarm backend with default settings. (Default: ```false```)
`--providers.swarm.allowemptyservices`:
Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. (Default: ```false```)
`--providers.swarm.constraints`:
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
`--providers.swarm.defaultrule`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
`--providers.swarm.endpoint`:
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
`--providers.swarm.exposedbydefault`:
Expose containers by default. (Default: ```true```)
`--providers.swarm.httpclienttimeout`:
Client timeout for HTTP connections. (Default: ```0```)
`--providers.swarm.network`:
Default Docker network used.
`--providers.swarm.refreshseconds`:
Polling interval for swarm mode. (Default: ```15```)
`--providers.swarm.tls.ca`:
TLS CA
`--providers.swarm.tls.cert`:
TLS cert
`--providers.swarm.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--providers.swarm.tls.key`:
TLS key
`--providers.swarm.usebindportip`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`--providers.swarm.watch`:
Watch Docker events. (Default: ```true```)
`--providers.zookeeper`: `--providers.zookeeper`:
Enable ZooKeeper backend with default settings. (Default: ```false```) Enable ZooKeeper backend with default settings. (Default: ```false```)

View file

@ -532,7 +532,7 @@ Constraints is an expression that Traefik matches against the container's labels
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```) Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
`TRAEFIK_PROVIDERS_DOCKER_ENDPOINT`: `TRAEFIK_PROVIDERS_DOCKER_ENDPOINT`:
Docker server endpoint. Can be a tcp or a unix socket endpoint. (Default: ```unix:///var/run/docker.sock```) Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
`TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT`: `TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT`:
Expose containers by default. (Default: ```true```) Expose containers by default. (Default: ```true```)
@ -543,12 +543,6 @@ Client timeout for HTTP connections. (Default: ```0```)
`TRAEFIK_PROVIDERS_DOCKER_NETWORK`: `TRAEFIK_PROVIDERS_DOCKER_NETWORK`:
Default Docker network used. Default Docker network used.
`TRAEFIK_PROVIDERS_DOCKER_SWARMMODE`:
Use Docker on Swarm Mode. (Default: ```false```)
`TRAEFIK_PROVIDERS_DOCKER_SWARMMODEREFRESHSECONDS`:
Polling interval for swarm mode. (Default: ```15```)
`TRAEFIK_PROVIDERS_DOCKER_TLS_CA`: `TRAEFIK_PROVIDERS_DOCKER_TLS_CA`:
TLS CA TLS CA
@ -855,6 +849,51 @@ Enable Rest backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_REST_INSECURE`: `TRAEFIK_PROVIDERS_REST_INSECURE`:
Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```) Activate REST Provider directly on the entryPoint named traefik. (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM`:
Enable Docker Swarm backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM_ALLOWEMPTYSERVICES`:
Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM_CONSTRAINTS`:
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.
`TRAEFIK_PROVIDERS_SWARM_DEFAULTRULE`:
Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
`TRAEFIK_PROVIDERS_SWARM_ENDPOINT`:
Docker server endpoint. Can be a TCP or a Unix socket endpoint. (Default: ```unix:///var/run/docker.sock```)
`TRAEFIK_PROVIDERS_SWARM_EXPOSEDBYDEFAULT`:
Expose containers by default. (Default: ```true```)
`TRAEFIK_PROVIDERS_SWARM_HTTPCLIENTTIMEOUT`:
Client timeout for HTTP connections. (Default: ```0```)
`TRAEFIK_PROVIDERS_SWARM_NETWORK`:
Default Docker network used.
`TRAEFIK_PROVIDERS_SWARM_REFRESHSECONDS`:
Polling interval for swarm mode. (Default: ```15```)
`TRAEFIK_PROVIDERS_SWARM_TLS_CA`:
TLS CA
`TRAEFIK_PROVIDERS_SWARM_TLS_CERT`:
TLS cert
`TRAEFIK_PROVIDERS_SWARM_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM_TLS_KEY`:
TLS key
`TRAEFIK_PROVIDERS_SWARM_USEBINDPORTIP`:
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
`TRAEFIK_PROVIDERS_SWARM_WATCH`:
Watch Docker events. (Default: ```true```)
`TRAEFIK_PROVIDERS_ZOOKEEPER`: `TRAEFIK_PROVIDERS_ZOOKEEPER`:
Enable ZooKeeper backend with default settings. (Default: ```false```) Enable ZooKeeper backend with default settings. (Default: ```false```)

View file

@ -83,9 +83,7 @@
defaultRule = "foobar" defaultRule = "foobar"
exposedByDefault = true exposedByDefault = true
useBindPortIP = true useBindPortIP = true
swarmMode = true
network = "foobar" network = "foobar"
swarmModeRefreshSeconds = "42s"
httpClientTimeout = "42s" httpClientTimeout = "42s"
allowEmptyServices = true allowEmptyServices = true
[providers.docker.tls] [providers.docker.tls]
@ -93,6 +91,22 @@
cert = "foobar" cert = "foobar"
key = "foobar" key = "foobar"
insecureSkipVerify = true insecureSkipVerify = true
[providers.swarm]
constraints = "foobar"
watch = true
endpoint = "foobar"
defaultRule = "foobar"
exposedByDefault = true
useBindPortIP = true
network = "foobar"
refreshSeconds = "42s"
httpClientTimeout = "42s"
allowEmptyServices = true
[providers.swarm.tls]
ca = "foobar"
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.file] [providers.file]
directory = "foobar" directory = "foobar"
watch = true watch = true

View file

@ -95,9 +95,23 @@ providers:
insecureSkipVerify: true insecureSkipVerify: true
exposedByDefault: true exposedByDefault: true
useBindPortIP: true useBindPortIP: true
swarmMode: true
network: foobar network: foobar
swarmModeRefreshSeconds: 42s httpClientTimeout: 42s
allowEmptyServices: true
swarm:
constraints: foobar
watch: true
endpoint: foobar
defaultRule: foobar
tls:
ca: foobar
cert: foobar
key: foobar
insecureSkipVerify: true
exposedByDefault: true
useBindPortIP: true
network: foobar
refreshSeconds: 42s
httpClientTimeout: 42s httpClientTimeout: 42s
allowEmptyServices: true allowEmptyServices: true
file: file:

View file

@ -83,54 +83,6 @@ Attach labels to your containers and let Traefik do the rest!
- traefik.http.services.admin-service.loadbalancer.server.port=9000 - traefik.http.services.admin-service.loadbalancer.server.port=9000
``` ```
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
Enabling the docker provider (Swarm Mode)
```yaml tab="File (YAML)"
providers:
docker:
# swarm classic (1.12-)
# endpoint: "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint: "tcp://127.0.0.1:2377"
swarmMode: true
```
```toml tab="File (TOML)"
[providers.docker]
# swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint = "tcp://127.0.0.1:2377"
swarmMode = true
```
```bash tab="CLI"
# swarm classic (1.12-)
# --providers.docker.endpoint=tcp://127.0.0.1:2375
# docker swarm mode (1.12+)
--providers.docker.endpoint=tcp://127.0.0.1:2377
--providers.docker.swarmMode=true
```
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
```yaml
version: "3"
services:
my-container:
deploy:
labels:
- traefik.http.routers.my-container.rule=Host(`example.com`)
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
```
!!! important "Labels in Docker Swarm Mode"
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)).
## Routing Configuration ## Routing Configuration
!!! info "Labels" !!! info "Labels"
@ -275,9 +227,6 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
Registers a port. Registers a port.
Useful when the container exposes multiples ports. Useful when the container exposes multiples ports.
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection_1)).
{: #port }
```yaml ```yaml
- "traefik.http.services.myservice.loadbalancer.server.port=8080" - "traefik.http.services.myservice.loadbalancer.server.port=8080"
``` ```
@ -675,14 +624,3 @@ otherwise it will randomly pick one (depending on how docker is returning them).
!!! warning !!! warning
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`. When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
#### `traefik.docker.lbswarm`
```yaml
- "traefik.docker.lbswarm=true"
```
Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.

View file

@ -0,0 +1,640 @@
---
title: "Traefik Docker Swarm Routing Documentation"
description: "This guide will teach you how to attach labels to your containers, to route traffic and load balance with Traefik and Docker."
---
# Traefik & Docker Swarm
A Story of Labels & Containers
{: .subtitle }
![Swarm](../../assets/img/providers/docker.png)
Attach labels to your containers and let Traefik do the rest!
## Configuration Examples
??? example "Configuring Docker Swarm & Deploying / Exposing Services"
Enabling the docker provider (Swarm Mode)
```yaml tab="File (YAML)"
providers:
swarm:
# swarm classic (1.12-)
# endpoint: "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint: "tcp://127.0.0.1:2377"
```
```toml tab="File (TOML)"
[providers.swarm]
# swarm classic (1.12-)
# endpoint = "tcp://127.0.0.1:2375"
# docker swarm mode (1.12+)
endpoint = "tcp://127.0.0.1:2377"
```
```bash tab="CLI"
# swarm classic (1.12-)
# --providers.swarm.endpoint=tcp://127.0.0.1:2375
# docker swarm mode (1.12+)
--providers.swarm.endpoint=tcp://127.0.0.1:2377
```
Attach labels to services (not to containers) while in Swarm mode (in your docker compose file)
```yaml
version: "3"
services:
my-container:
deploy:
labels:
- traefik.http.routers.my-container.rule=Host(`example.com`)
- traefik.http.services.my-container-service.loadbalancer.server.port=8080
```
!!! important "Labels in Docker Swarm Mode"
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)).
??? example "Specifying more than one router and service per container"
Forwarding requests to more than one port on a container requires referencing the service loadbalancer port definition using the service parameter on the router.
In this example, requests are forwarded for `http://example-a.com` to `http://<private IP of container>:8000` in addition to `http://example-b.com` forwarding to `http://<private IP of container>:9000`:
```yaml
version: "3"
services:
my-container:
# ...
deploy:
labels:
- traefik.http.routers.www-router.rule=Host(`example-a.com`)
- traefik.http.routers.www-router.service=www-service
- traefik.http.services.www-service.loadbalancer.server.port=8000
- traefik.http.routers.admin-router.rule=Host(`example-b.com`)
- traefik.http.routers.admin-router.service=admin-service
- traefik.http.services.admin-service.loadbalancer.server.port=9000
```
## Routing Configuration
!!! info "Labels"
- Labels are case insensitive.
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
### General
Traefik creates, for each container, a corresponding [service](../services/index.md) and [router](../routers/index.md).
The Service automatically gets a server per instance of the container,
and the router automatically gets a rule defined by `defaultRule` (if no rule for it was defined in labels).
#### Service definition
--8<-- "content/routing/providers/service-by-label.md"
??? example "Automatic service assignment with labels"
With labels in a compose file
```yaml
labels:
- "traefik.http.routers.myproxy.rule=Host(`example.net`)"
# service myservice gets automatically assigned to router myproxy
- "traefik.http.services.myservice.loadbalancer.server.port=80"
```
??? example "Automatic service creation and assignment with labels"
With labels in a compose file
```yaml
labels:
# no service specified or defined and yet one gets automatically created
# and assigned to router myproxy.
- "traefik.http.routers.myproxy.rule=Host(`example.net`)"
```
### Routers
To update the configuration of the Router automatically attached to the container,
add labels starting with `traefik.http.routers.<name-of-your-choice>.` and followed by the option you want to change.
For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`example.com`)```.
!!! warning "The character `@` is not authorized in the router name `<router_name>`."
??? info "`traefik.http.routers.<router_name>.rule`"
See [rule](../routers/index.md#rule) for more information.
```yaml
- "traefik.http.routers.myrouter.rule=Host(`example.com`)"
```
??? info "`traefik.http.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints) for more information.
```yaml
- "traefik.http.routers.myrouter.entrypoints=ep1,ep2"
```
??? info "`traefik.http.routers.<router_name>.middlewares`"
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
```yaml
- "traefik.http.routers.myrouter.middlewares=auth,prefix,cb"
```
??? info "`traefik.http.routers.<router_name>.service`"
See [service](../routers/index.md#service) for more information.
```yaml
- "traefik.http.routers.myrouter.service=myservice"
```
??? info "`traefik.http.routers.<router_name>.tls`"
See [tls](../routers/index.md#tls) for more information.
```yaml
- "traefik.http.routers.myrouter.tls=true"
```
??? info "`traefik.http.routers.<router_name>.tls.certresolver`"
See [certResolver](../routers/index.md#certresolver) for more information.
```yaml
- "traefik.http.routers.myrouter.tls.certresolver=myresolver"
```
??? info "`traefik.http.routers.<router_name>.tls.domains[n].main`"
See [domains](../routers/index.md#domains) for more information.
```yaml
- "traefik.http.routers.myrouter.tls.domains[0].main=example.org"
```
??? info "`traefik.http.routers.<router_name>.tls.domains[n].sans`"
See [domains](../routers/index.md#domains) for more information.
```yaml
- "traefik.http.routers.myrouter.tls.domains[0].sans=test.example.org,dev.example.org"
```
??? info "`traefik.http.routers.<router_name>.tls.options`"
See [options](../routers/index.md#options) for more information.
```yaml
- "traefik.http.routers.myrouter.tls.options=foobar"
```
??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information.
```yaml
- "traefik.http.routers.myrouter.priority=42"
```
### Services
To update the configuration of the Service automatically attached to the container,
add labels starting with `traefik.http.services.<name-of-your-choice>.`, followed by the option you want to change.
For example, to change the `passHostHeader` behavior,
you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.passhostheader=false`.
!!! warning "The character `@` is not authorized in the service name `<service_name>`."
??? info "`traefik.http.services.<service_name>.loadbalancer.server.port`"
Registers a port.
Useful when the container exposes multiples ports.
Mandatory for Docker Swarm (see the section ["Port Detection with Docker Swarm"](../../providers/docker.md#port-detection)).
{: #port }
```yaml
- "traefik.http.services.myservice.loadbalancer.server.port=8080"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.server.scheme`"
Overrides the default scheme.
```yaml
- "traefik.http.services.myservice.loadbalancer.server.scheme=http"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.serverstransport`"
Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../services/index.md#serverstransport) for more information.
```yaml
- "traefik.http.services.<service_name>.loadbalancer.serverstransport=foobar@file"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.passhostheader`"
See [pass Host header](../services/index.md#pass-host-header) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.passhostheader=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.headers.<header_name>`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.hostname`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=example.org"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.interval`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.path`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.method`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.method=foobar"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.status`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.status=42"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.port`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.port=42"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.scheme`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.timeout`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10s"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.httponly`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.name`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.secure`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky.cookie.samesite`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.sticky.cookie.samesite=none"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.responseforwarding.flushinterval`"
See [response forwarding](../services/index.md#response-forwarding) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10"
```
### Middleware
You can declare pieces of middleware using labels starting with `traefik.http.middlewares.<name-of-your-choice>.`,
followed by the middleware type/options.
For example, to declare a middleware [`redirectscheme`](../../middlewares/http/redirectscheme.md) named `my-redirect`,
you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme=https`.
More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md).
!!! warning "The character `@` is not authorized in the middleware name."
??? example "Declaring and Referencing a Middleware"
```yaml
services:
my-container:
# ...
deploy:
labels:
# Declaring a middleware
- traefik.http.middlewares.my-redirect.redirectscheme.scheme=https
# Referencing a middleware
- traefik.http.routers.my-container.middlewares=my-redirect
```
!!! warning "Conflicts in Declaration"
If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared.
### TCP
You can declare TCP Routers and/or Services using labels.
??? example "Declaring TCP Routers and Services"
```yaml
services:
my-container:
# ...
deploy:
labels:
- "traefik.tcp.routers.my-router.rule=HostSNI(`example.com`)"
- "traefik.tcp.routers.my-router.tls=true"
- "traefik.tcp.services.my-service.loadbalancer.server.port=4123"
```
!!! warning "TCP and HTTP"
If you declare a TCP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no TCP Router/Service is defined).
You can declare both a TCP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
#### TCP Routers
??? info "`traefik.tcp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2"
```
??? info "`traefik.tcp.routers.<router_name>.rule`"
See [rule](../routers/index.md#rule_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)"
```
??? info "`traefik.tcp.routers.<router_name>.service`"
See [service](../routers/index.md#services) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.service=myservice"
```
??? info "`traefik.tcp.routers.<router_name>.tls`"
See [TLS](../routers/index.md#tls_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls=true"
```
??? info "`traefik.tcp.routers.<router_name>.tls.certresolver`"
See [certResolver](../routers/index.md#certresolver_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver"
```
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].main`"
See [domains](../routers/index.md#domains_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls.domains[0].main=example.org"
```
??? info "`traefik.tcp.routers.<router_name>.tls.domains[n].sans`"
See [domains](../routers/index.md#domains_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.example.org,dev.example.org"
```
??? info "`traefik.tcp.routers.<router_name>.tls.options`"
See [options](../routers/index.md#options_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls.options=mysoptions"
```
??? info "`traefik.tcp.routers.<router_name>.tls.passthrough`"
See [TLS](../routers/index.md#tls_1) for more information.
```yaml
- "traefik.tcp.routers.mytcprouter.tls.passthrough=true"
```
??? info "`traefik.tcp.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority_1) for more information.
```yaml
- "traefik.tcp.routers.myrouter.priority=42"
```
#### TCP Services
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.server.tls`"
Determines whether to use TLS when dialing with the backend.
```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.proxyprotocol.version`"
See [PROXY protocol](../services/index.md#proxy-protocol) for more information.
```yaml
- "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1"
```
??? info "`traefik.tcp.services.<service_name>.loadbalancer.serverstransport`"
Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../services/index.md#serverstransport_2) for more information.
```yaml
- "traefik.tcp.services.<service_name>.loadbalancer.serverstransport=foobar@file"
```
### UDP
You can declare UDP Routers and/or Services using labels.
??? example "Declaring UDP Routers and Services"
```yaml
services:
my-container:
# ...
deploy:
labels:
- "traefik.udp.routers.my-router.entrypoints=udp"
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
```
!!! warning "UDP and HTTP"
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
You can declare both a UDP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
#### UDP Routers
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_2) for more information.
```yaml
- "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2"
```
??? info "`traefik.udp.routers.<router_name>.service`"
See [service](../routers/index.md#services_1) for more information.
```yaml
- "traefik.udp.routers.myudprouter.service=myservice"
```
#### UDP Services
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```yaml
- "traefik.udp.services.myudpservice.loadbalancer.server.port=423"
```
### Specific Provider Options
#### `traefik.enable`
```yaml
- "traefik.enable=true"
```
You can tell Traefik to consider (or not) the container by setting `traefik.enable` to true or false.
This option overrides the value of `exposedByDefault`.
#### `traefik.docker.network`
```yaml
- "traefik.docker.network=mynetwork"
```
Overrides the default docker network to use for connections to the container.
If a container is linked to several networks, be sure to set the proper network name (you can check this with `docker inspect <container_id>`),
otherwise it will randomly pick one (depending on how docker is returning them).
!!! warning
When deploying a stack from a compose file `stack`, the networks defined are prefixed with `stack`.
#### `traefik.docker.lbswarm`
```yaml
- "traefik.docker.lbswarm=true"
```
Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode).
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.

View file

@ -75,6 +75,7 @@ nav:
- 'Configuration Discovery': - 'Configuration Discovery':
- 'Overview': 'providers/overview.md' - 'Overview': 'providers/overview.md'
- 'Docker': 'providers/docker.md' - 'Docker': 'providers/docker.md'
- 'Swarm': 'providers/swarm.md'
- 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md' - 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md'
- 'Kubernetes Ingress': 'providers/kubernetes-ingress.md' - 'Kubernetes Ingress': 'providers/kubernetes-ingress.md'
- 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md' - 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md'
@ -94,6 +95,7 @@ nav:
- 'Services': 'routing/services/index.md' - 'Services': 'routing/services/index.md'
- 'Providers': - 'Providers':
- 'Docker': 'routing/providers/docker.md' - 'Docker': 'routing/providers/docker.md'
- 'Swarm': 'routing/providers/swarm.md'
- 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md' - 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md'
- 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md' - 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md'
- 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md' - 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md'
@ -196,6 +198,7 @@ nav:
- 'Dynamic Configuration': - 'Dynamic Configuration':
- 'File': 'reference/dynamic-configuration/file.md' - 'File': 'reference/dynamic-configuration/file.md'
- 'Docker': 'reference/dynamic-configuration/docker.md' - 'Docker': 'reference/dynamic-configuration/docker.md'
- 'Swarm': 'reference/dynamic-configuration/swarm.md'
- 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md' - 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md'
- 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md' - 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md'
- 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md' - 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md'

View file

@ -235,6 +235,7 @@ func TestHandler_Overview(t *testing.T) {
API: &static.API{}, API: &static.API{},
Providers: &static.Providers{ Providers: &static.Providers{
Docker: &docker.Provider{}, Docker: &docker.Provider{},
Swarm: &docker.SwarmProvider{},
File: &file.Provider{}, File: &file.Provider{},
KubernetesIngress: &ingress.Provider{}, KubernetesIngress: &ingress.Provider{},
KubernetesCRD: &crd.Provider{}, KubernetesCRD: &crd.Provider{},

View file

@ -24,6 +24,7 @@
}, },
"providers": [ "providers": [
"Docker", "Docker",
"Swarm",
"File", "File",
"KubernetesIngress", "KubernetesIngress",
"KubernetesCRD", "KubernetesCRD",

View file

@ -40,9 +40,7 @@
defaultRule = "foobar" defaultRule = "foobar"
exposedByDefault = true exposedByDefault = true
useBindPortIP = true useBindPortIP = true
swarmMode = true
network = "foobar" network = "foobar"
swarmModeRefreshSeconds = 42
httpClientTimeout = 42 httpClientTimeout = 42
[providers.docker.tls] [providers.docker.tls]
ca = "foobar" ca = "foobar"

View file

@ -211,6 +211,8 @@ type Providers struct {
ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"` ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Swarm *docker.SwarmProvider `description:"Enable Docker Swarm backend with default settings." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"` File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
@ -219,7 +221,6 @@ type Providers struct {
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
@ -265,15 +266,21 @@ func (c *Configuration) SetEffectiveConfiguration() {
} }
if c.Providers.Docker != nil { if c.Providers.Docker != nil {
if c.Providers.Docker.SwarmModeRefreshSeconds <= 0 {
c.Providers.Docker.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
}
if c.Providers.Docker.HTTPClientTimeout < 0 { if c.Providers.Docker.HTTPClientTimeout < 0 {
c.Providers.Docker.HTTPClientTimeout = 0 c.Providers.Docker.HTTPClientTimeout = 0
} }
} }
if c.Providers.Swarm != nil {
if c.Providers.Swarm.RefreshSeconds <= 0 {
c.Providers.Swarm.RefreshSeconds = ptypes.Duration(15 * time.Second)
}
if c.Providers.Swarm.HTTPClientTimeout < 0 {
c.Providers.Swarm.HTTPClientTimeout = 0
}
}
// Disable Gateway API provider if not enabled in experimental. // Disable Gateway API provider if not enabled in experimental.
if c.Experimental == nil || !c.Experimental.KubernetesGateway { if c.Experimental == nil || !c.Experimental.KubernetesGateway {
c.Providers.KubernetesGateway = nil c.Providers.KubernetesGateway = nil

View file

@ -80,6 +80,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
p.quietAddProvider(conf.Docker) p.quietAddProvider(conf.Docker)
} }
if conf.Swarm != nil {
p.quietAddProvider(conf.Swarm)
}
if conf.Rest != nil { if conf.Rest != nil {
p.quietAddProvider(conf.Rest) p.quietAddProvider(conf.Rest)
} }

View file

@ -8,6 +8,7 @@ import (
"strings" "strings"
dockertypes "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
@ -17,7 +18,16 @@ import (
"github.com/traefik/traefik/v3/pkg/provider/constraints" "github.com/traefik/traefik/v3/pkg/provider/constraints"
) )
func (p *Provider) buildConfiguration(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration { type DynConfBuilder struct {
Shared
apiClient client.APIClient
}
func NewDynConfBuilder(configuration Shared, apiClient client.APIClient) *DynConfBuilder {
return &DynConfBuilder{Shared: configuration, apiClient: apiClient}
}
func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration {
configurations := make(map[string]*dynamic.Configuration) configurations := make(map[string]*dynamic.Configuration)
for _, container := range containersInspected { for _, container := range containersInspected {
@ -92,7 +102,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
return provider.Merge(ctx, configurations) return provider.Merge(ctx, configurations)
} }
func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.TCPConfiguration) error { func (p *DynConfBuilder) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.TCPConfiguration) error {
serviceName := getServiceName(container) serviceName := getServiceName(container)
if len(configuration.Services) == 0 { if len(configuration.Services) == 0 {
@ -117,7 +127,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
return nil return nil
} }
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error { func (p *DynConfBuilder) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error {
serviceName := getServiceName(container) serviceName := getServiceName(container)
if len(configuration.Services) == 0 { if len(configuration.Services) == 0 {
@ -141,7 +151,7 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
return nil return nil
} }
func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error { func (p *DynConfBuilder) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error {
serviceName := getServiceName(container) serviceName := getServiceName(container)
if len(configuration.Services) == 0 { if len(configuration.Services) == 0 {
@ -167,7 +177,7 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
return nil return nil
} }
func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool { func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData) bool {
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
if !container.ExtraConf.Enable { if !container.ExtraConf.Enable {
@ -193,7 +203,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return true return true
} }
func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPServersLoadBalancer) error { func (p *DynConfBuilder) addServerTCP(ctx context.Context, container dockerData, loadBalancer *dynamic.TCPServersLoadBalancer) error {
if loadBalancer == nil { if loadBalancer == nil {
return errors.New("load-balancer is not defined") return errors.New("load-balancer is not defined")
} }
@ -219,7 +229,7 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB
return nil return nil
} }
func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadBalancer *dynamic.UDPServersLoadBalancer) error { func (p *DynConfBuilder) addServerUDP(ctx context.Context, container dockerData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
if loadBalancer == nil { if loadBalancer == nil {
return errors.New("load-balancer is not defined") return errors.New("load-balancer is not defined")
} }
@ -245,7 +255,7 @@ func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadB
return nil return nil
} }
func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error { func (p *DynConfBuilder) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error {
if loadBalancer == nil { if loadBalancer == nil {
return errors.New("load-balancer is not defined") return errors.New("load-balancer is not defined")
} }
@ -275,7 +285,7 @@ func (p *Provider) addServer(ctx context.Context, container dockerData, loadBala
return nil return nil
} }
func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPort string) (string, string, error) { func (p *DynConfBuilder) getIPPort(ctx context.Context, container dockerData, serverPort string) (string, string, error) {
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
var ip, port string var ip, port string
@ -307,7 +317,7 @@ func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPo
return ip, port, nil return ip, port, nil
} }
func (p Provider) getIPAddress(ctx context.Context, container dockerData) string { func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData) string {
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
netNotFound := false netNotFound := false
@ -338,23 +348,17 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
} }
if container.NetworkSettings.NetworkMode.IsContainer() { if container.NetworkSettings.NetworkMode.IsContainer() {
dockerClient, err := p.createClient()
if err != nil {
logger.Warn().Err(err).Msg("Unable to get IP address")
return ""
}
connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer() connectedContainer := container.NetworkSettings.NetworkMode.ConnectedContainer()
containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer) containerInspected, err := p.apiClient.ContainerInspect(context.Background(), connectedContainer)
if err != nil { if err != nil {
logger.Warn().Err(err).Msgf("Unable to get IP address for container %s: failed to inspect container ID %s", container.Name, connectedContainer) logger.Warn().Err(err).Msgf("Unable to get IP address for container %s: failed to inspect container ID %s", container.Name, connectedContainer)
return "" return ""
} }
// Check connected container for traefik.docker.network, falling back to // Check connected container for traefik.docker.network,
// the network specified on the current container. // falling back to the network specified on the current container.
containerParsed := parseContainer(containerInspected) containerParsed := parseContainer(containerInspected)
extraConf, err := p.getConfiguration(containerParsed) extraConf, err := p.extractLabels(containerParsed)
if err != nil { if err != nil {
logger.Warn().Err(err).Msgf("Unable to get IP address for container %s : failed to get extra configuration for container %s", container.Name, containerInspected.Name) logger.Warn().Err(err).Msgf("Unable to get IP address for container %s : failed to get extra configuration for container %s", container.Name, containerInspected.Name)
return "" return ""
@ -379,8 +383,9 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
return "" return ""
} }
func (p *Provider) getPortBinding(container dockerData, serverPort string) (*nat.PortBinding, error) { func (p *DynConfBuilder) getPortBinding(container dockerData, serverPort string) (*nat.PortBinding, error) {
port := getPort(container, serverPort) port := getPort(container, serverPort)
for netPort, portBindings := range container.NetworkSettings.Ports { for netPort, portBindings := range container.NetworkSettings.Ports {
if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") { if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") {
for _, p := range portBindings { for _, p := range portBindings {
@ -391,36 +396,3 @@ func (p *Provider) getPortBinding(container dockerData, serverPort string) (*nat
return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name) return nil, fmt.Errorf("unable to find the external IP:Port for the container %q", container.Name)
} }
func getPort(container dockerData, serverPort string) string {
if len(serverPort) > 0 {
return serverPort
}
var ports []nat.Port
for port := range container.NetworkSettings.Ports {
ports = append(ports, port)
}
less := func(i, j nat.Port) bool {
return i.Int() < j.Int()
}
nat.Sort(ports, less)
if len(ports) > 0 {
min := ports[0]
return min.Port()
}
return ""
}
func getServiceName(container dockerData) string {
serviceName := container.ServiceName
if values, err := getStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject]
}
return provider.Normalize(serviceName)
}

View file

@ -15,7 +15,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
) )
func TestDefaultRule(t *testing.T) { func TestDynConfBuilder_DefaultRule(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
containers []dockerData containers []dockerData
@ -376,8 +376,10 @@ func TestDefaultRule(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{ p := Provider{
Shared: Shared{
ExposedByDefault: true, ExposedByDefault: true,
DefaultRule: test.defaultRule, DefaultRule: test.defaultRule,
},
} }
err := p.Init() err := p.Init()
@ -385,18 +387,20 @@ func TestDefaultRule(t *testing.T) {
for i := 0; i < len(test.containers); i++ { for i := 0; i < len(test.containers); i++ {
var err error var err error
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i]) test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i])
require.NoError(t, err) require.NoError(t, err)
} }
configuration := p.buildConfiguration(context.Background(), test.containers) builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration) assert.Equal(t, test.expected, configuration)
}) })
} }
} }
func Test_buildConfiguration(t *testing.T) { func TestDynConfBuilder_build(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
containers []dockerData containers []dockerData
@ -3381,10 +3385,12 @@ func Test_buildConfiguration(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{ p := Provider{
Shared: Shared{
AllowEmptyServices: test.allowEmptyServices, AllowEmptyServices: test.allowEmptyServices,
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
ExposedByDefault: true, ExposedByDefault: true,
UseBindPortIP: test.useBindPortIP, UseBindPortIP: test.useBindPortIP,
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
},
} }
p.Constraints = test.constraints p.Constraints = test.constraints
@ -3393,18 +3399,20 @@ func Test_buildConfiguration(t *testing.T) {
for i := 0; i < len(test.containers); i++ { for i := 0; i < len(test.containers); i++ {
var err error var err error
test.containers[i].ExtraConf, err = p.getConfiguration(test.containers[i]) test.containers[i].ExtraConf, err = p.extractLabels(test.containers[i])
require.NoError(t, err) require.NoError(t, err)
} }
configuration := p.buildConfiguration(context.Background(), test.containers) builder := NewDynConfBuilder(p.Shared, nil)
configuration := builder.build(context.Background(), test.containers)
assert.Equal(t, test.expected, configuration) assert.Equal(t, test.expected, configuration)
}) })
} }
} }
func TestDockerGetIPPort(t *testing.T) { func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
type expected struct { type expected struct {
ip string ip string
port string port string
@ -3565,12 +3573,12 @@ func TestDockerGetIPPort(t *testing.T) {
dData := parseContainer(test.container) dData := parseContainer(test.container)
provider := &Provider{ builder := NewDynConfBuilder(Shared{
Network: "testnet", Network: "testnet",
UseBindPortIP: true, UseBindPortIP: true,
} }, nil)
actualIP, actualPort, actualError := provider.getIPPort(context.Background(), dData, test.serverPort) actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort)
if test.expected.error { if test.expected.error {
require.Error(t, actualError) require.Error(t, actualError)
} else { } else {
@ -3582,73 +3590,7 @@ func TestDockerGetIPPort(t *testing.T) {
} }
} }
func TestDockerGetPort(t *testing.T) { func TestDynConfBuilder_getIPAddress_docker(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serverPort string
expected string
}{
{
desc: "no binding, no server port label",
container: containerJSON(name("foo")),
expected: "",
},
{
desc: "binding, no server port label",
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
})),
expected: "80",
},
{
desc: "binding, multiple ports, no server port label",
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
"443/tcp": {},
})),
expected: "80",
},
{
desc: "no binding, server port label",
container: containerJSON(),
serverPort: "8080",
expected: "8080",
},
{
desc: "binding, server port label",
container: containerJSON(
ports(nat.PortMap{
"80/tcp": {},
})),
serverPort: "8080",
expected: "8080",
},
{
desc: "binding, multiple ports, server port label",
container: containerJSON(ports(nat.PortMap{
"8080/tcp": {},
"80/tcp": {},
})),
serverPort: "8080",
expected: "8080",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getPort(dData, test.serverPort)
assert.Equal(t, test.expected, actual)
})
}
}
func TestDockerGetIPAddress(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
container docker.ContainerJSON container docker.ContainerJSON
@ -3742,24 +3684,26 @@ func TestDockerGetIPAddress(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
provider := &Provider{ conf := Shared{
Network: "webnet", Network: "webnet",
} }
dData := parseContainer(test.container) dData := parseContainer(test.container)
dData.ExtraConf.Docker.Network = provider.Network dData.ExtraConf.Docker.Network = conf.Network
if len(test.network) > 0 { if len(test.network) > 0 {
dData.ExtraConf.Docker.Network = test.network dData.ExtraConf.Docker.Network = test.network
} }
actual := provider.getIPAddress(context.Background(), dData) builder := NewDynConfBuilder(conf, nil)
actual := builder.getIPAddress(context.Background(), dData)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }
} }
func TestSwarmGetIPAddress(t *testing.T) { func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
testCases := []struct { testCases := []struct {
service swarm.Service service swarm.Service
expected string expected string
@ -3810,47 +3754,13 @@ func TestSwarmGetIPAddress(t *testing.T) {
t.Run(strconv.Itoa(serviceID), func(t *testing.T) { t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel() t.Parallel()
provider := &Provider{ p := &SwarmProvider{}
SwarmMode: true,
}
dData, err := provider.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err)
actual := provider.getIPAddress(context.Background(), dData)
assert.Equal(t, test.expected, actual)
})
}
}
func TestSwarmGetPort(t *testing.T) {
testCases := []struct {
service swarm.Service
serverPort string
networks map[string]*docker.NetworkResource
expected string
}{
{
service: swarmService(
withEndpointSpec(modeDNSSR),
),
networks: map[string]*docker.NetworkResource{},
serverPort: "8080",
expected: "8080",
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
p := Provider{}
dData, err := p.parseService(context.Background(), test.service, test.networks) dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)
actual := getPort(dData, test.serverPort) builder := NewDynConfBuilder(p.Shared, nil)
actual := builder.getIPAddress(context.Background(), dData)
assert.Equal(t, test.expected, actual) assert.Equal(t, test.expected, actual)
}) })
} }

View file

@ -0,0 +1,35 @@
package docker
import (
dockertypes "github.com/docker/docker/api/types"
dockercontainertypes "github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
)
// dockerData holds the need data to the provider.
type dockerData struct {
ID string
ServiceName string
Name string
Labels map[string]string // List of labels set to container or service
NetworkSettings networkSettings
Health string
Node *dockertypes.ContainerNode
ExtraConf configuration
}
// NetworkSettings holds the networks data to the provider.
type networkSettings struct {
NetworkMode dockercontainertypes.NetworkMode
Ports nat.PortMap
Networks map[string]*networkData
}
// Network holds the network data to the provider.
type networkData struct {
Name string
Addr string
Port int
Protocol string
ID string
}

View file

@ -1,602 +0,0 @@
package docker
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/docker/cli/cli/connhelper"
dockertypes "github.com/docker/docker/api/types"
dockercontainertypes "github.com/docker/docker/api/types/container"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/docker/go-connections/sockets"
"github.com/rs/zerolog/log"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/traefik/traefik/v3/pkg/version"
)
const (
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use.
DockerAPIVersion = "1.24"
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
SwarmAPIVersion = "1.24"
)
// DefaultTemplateRule The default template for the default rule.
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
ExposedByDefault bool `description:"Expose containers by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network." json:"useBindPortIP,omitempty" toml:"useBindPortIP,omitempty" yaml:"useBindPortIP,omitempty" export:"true"`
SwarmMode bool `description:"Use Docker on Swarm Mode." json:"swarmMode,omitempty" toml:"swarmMode,omitempty" yaml:"swarmMode,omitempty" export:"true"`
Network string `description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,omitempty" export:"true"`
SwarmModeRefreshSeconds ptypes.Duration `description:"Polling interval for swarm mode." json:"swarmModeRefreshSeconds,omitempty" toml:"swarmModeRefreshSeconds,omitempty" yaml:"swarmModeRefreshSeconds,omitempty" export:"true"`
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
AllowEmptyServices bool `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
defaultRuleTpl *template.Template
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.ExposedByDefault = true
p.Endpoint = "unix:///var/run/docker.sock"
p.SwarmMode = false
p.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
p.DefaultRule = DefaultTemplateRule
}
// Init the provider.
func (p *Provider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
if err != nil {
return fmt.Errorf("error while parsing default rule: %w", err)
}
p.defaultRuleTpl = defaultRuleTpl
return nil
}
// dockerData holds the need data to the provider.
type dockerData struct {
ID string
ServiceName string
Name string
Labels map[string]string // List of labels set to container or service
NetworkSettings networkSettings
Health string
Node *dockertypes.ContainerNode
ExtraConf configuration
}
// NetworkSettings holds the networks data to the provider.
type networkSettings struct {
NetworkMode dockercontainertypes.NetworkMode
Ports nat.PortMap
Networks map[string]*networkData
}
// Network holds the network data to the provider.
type networkData struct {
Name string
Addr string
Port int
Protocol string
ID string
}
func (p *Provider) createClient() (client.APIClient, error) {
opts, err := p.getClientOpts()
if err != nil {
return nil, err
}
httpHeaders := map[string]string{
"User-Agent": "Traefik " + version.Version,
}
opts = append(opts, client.WithHTTPHeaders(httpHeaders))
apiVersion := DockerAPIVersion
if p.SwarmMode {
apiVersion = SwarmAPIVersion
}
opts = append(opts, client.WithVersion(apiVersion))
return client.NewClientWithOpts(opts...)
}
func (p *Provider) getClientOpts() ([]client.Opt, error) {
helper, err := connhelper.GetConnectionHelper(p.Endpoint)
if err != nil {
return nil, err
}
// SSH
if helper != nil {
// https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: helper.Dialer,
},
}
return []client.Opt{
client.WithHTTPClient(httpClient),
client.WithTimeout(time.Duration(p.HTTPClientTimeout)),
client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error
client.WithDialContext(helper.Dialer),
}, nil
}
opts := []client.Opt{
client.WithHost(p.Endpoint),
client.WithTimeout(time.Duration(p.HTTPClientTimeout)),
}
if p.TLS != nil {
ctx := log.With().Str(logs.ProviderName, "docker").Logger().WithContext(context.Background())
conf, err := p.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
}
hostURL, err := client.ParseHostURL(p.Endpoint)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: conf,
}
if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil {
return nil, err
}
opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(p.HTTPClientTimeout)}))
}
return opts, nil
}
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
pool.GoCtx(func(routineCtx context.Context) {
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, "docker").Logger()
ctxLog := logger.WithContext(routineCtx)
operation := func() error {
var err error
ctx, cancel := context.WithCancel(ctxLog)
defer cancel()
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, "docker").Logger().WithContext(ctx)
dockerClient, err := p.createClient()
if err != nil {
logger.Error().Err(err).Msg("Failed to create a client for docker, error")
return err
}
defer dockerClient.Close()
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
return err
}
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
var dockerDataList []dockerData
if p.SwarmMode {
dockerDataList, err = p.listServices(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
return err
}
} else {
dockerDataList, err = p.listContainers(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list containers for docker")
return err
}
}
configuration := p.buildConfiguration(ctxLog, dockerDataList)
configurationChan <- dynamic.Message{
ProviderName: "docker",
Configuration: configuration,
}
if p.Watch {
if p.SwarmMode {
errChan := make(chan error)
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
ticker := time.NewTicker(time.Duration(p.SwarmModeRefreshSeconds))
pool.GoCtx(func(ctx context.Context) {
logger := log.Ctx(ctx).With().Str(logs.ProviderName, "docker").Logger()
ctx = logger.WithContext(ctx)
defer close(errChan)
for {
select {
case <-ticker.C:
services, err := p.listServices(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
errChan <- err
return
}
configuration := p.buildConfiguration(ctx, services)
if configuration != nil {
configurationChan <- dynamic.Message{
ProviderName: "docker",
Configuration: configuration,
}
}
case <-ctx.Done():
ticker.Stop()
return
}
}
})
if err, ok := <-errChan; ok {
return err
}
// channel closed
} else {
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
startStopHandle := func(m eventtypes.Message) {
logger.Debug().Msgf("Provider event received %+v", m)
containers, err := p.listContainers(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list containers for docker")
// Call cancel to get out of the monitor
return
}
configuration := p.buildConfiguration(ctx, containers)
if configuration != nil {
message := dynamic.Message{
ProviderName: "docker",
Configuration: configuration,
}
select {
case configurationChan <- message:
case <-ctx.Done():
}
}
}
eventsc, errc := dockerClient.Events(ctx, options)
for {
select {
case event := <-eventsc:
if event.Action == "start" ||
event.Action == "die" ||
strings.HasPrefix(event.Action, "health_status") {
startStopHandle(event)
}
case err := <-errc:
if errors.Is(err, io.EOF) {
logger.Debug().Msg("Provider event stream closed")
}
return err
case <-ctx.Done():
return nil
}
}
}
}
return nil
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})
return nil
}
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
if err != nil {
return nil, err
}
var inspectedContainers []dockerData
// get inspect containers
for _, container := range containerList {
dData := inspectContainers(ctx, dockerClient, container.ID)
if len(dData.Name) == 0 {
continue
}
extraConf, err := p.getConfiguration(dData)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue
}
dData.ExtraConf = extraConf
inspectedContainers = append(inspectedContainers, dData)
}
return inspectedContainers, nil
}
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
return dockerData{}
}
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
// We register only container which are running
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
return parseContainer(containerInspected)
}
return dockerData{}
}
func parseContainer(container dockertypes.ContainerJSON) dockerData {
dData := dockerData{
NetworkSettings: networkSettings{},
}
if container.ContainerJSONBase != nil {
dData.ID = container.ContainerJSONBase.ID
dData.Name = container.ContainerJSONBase.Name
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
dData.Node = container.ContainerJSONBase.Node
if container.ContainerJSONBase.HostConfig != nil {
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
}
if container.State != nil && container.State.Health != nil {
dData.Health = container.State.Health.Status
}
}
if container.Config != nil && container.Config.Labels != nil {
dData.Labels = container.Config.Labels
}
if container.NetworkSettings != nil {
if container.NetworkSettings.Ports != nil {
dData.NetworkSettings.Ports = container.NetworkSettings.Ports
}
if container.NetworkSettings.Networks != nil {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for name, containerNetwork := range container.NetworkSettings.Networks {
addr := containerNetwork.IPAddress
if addr == "" {
addr = containerNetwork.GlobalIPv6Address
}
dData.NetworkSettings.Networks[name] = &networkData{
ID: containerNetwork.NetworkID,
Name: name,
Addr: addr,
}
}
}
}
return dData
}
func (p *Provider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
logger := log.Ctx(ctx)
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
if err != nil {
return nil, err
}
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
return nil, err
}
networkListArgs := filters.NewArgs()
// https://docs.docker.com/engine/api/v1.29/#tag/Network (Docker 17.06)
if versions.GreaterThanOrEqualTo(serverVersion.APIVersion, "1.29") {
networkListArgs.Add("scope", "swarm")
} else {
networkListArgs.Add("driver", "overlay")
}
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
if err != nil {
logger.Debug().Err(err).Msg("Failed to network inspect on client for docker")
return nil, err
}
networkMap := make(map[string]*dockertypes.NetworkResource)
for _, network := range networkList {
networkToAdd := network
networkMap[network.ID] = &networkToAdd
}
var dockerDataList []dockerData
var dockerDataListTasks []dockerData
for _, service := range serviceList {
dData, err := p.parseService(ctx, service, networkMap)
if err != nil {
logger.Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue
}
if dData.ExtraConf.Docker.LBSwarm {
if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData)
}
} else {
isGlobalSvc := service.Spec.Mode.Global != nil
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc)
if err != nil {
logger.Warn().Err(err).Send()
} else {
dockerDataList = append(dockerDataList, dockerDataListTasks...)
}
}
}
return dockerDataList, err
}
func (p *Provider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) {
logger := log.Ctx(ctx)
dData := dockerData{
ID: service.ID,
ServiceName: service.Spec.Annotations.Name,
Name: service.Spec.Annotations.Name,
Labels: service.Spec.Annotations.Labels,
NetworkSettings: networkSettings{},
}
extraConf, err := p.getConfiguration(dData)
if err != nil {
return dockerData{}, err
}
dData.ExtraConf = extraConf
if service.Spec.EndpointSpec != nil {
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
if dData.ExtraConf.Docker.LBSwarm {
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
}
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]
if networkService != nil {
if len(virtualIP.Addr) > 0 {
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
network := &networkData{
Name: networkService.Name,
ID: virtualIP.NetworkID,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
} else {
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
}
} else {
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
}
}
}
}
return dData, nil
}
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
) ([]dockerData, error) {
serviceIDFilter := filters.NewArgs()
serviceIDFilter.Add("service", serviceID)
serviceIDFilter.Add("desired-state", "running")
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
if err != nil {
return nil, err
}
var dockerDataList []dockerData
for _, task := range taskList {
if task.Status.State != swarmtypes.TaskStateRunning {
continue
}
dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc)
if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData)
}
}
return dockerDataList, err
}
func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData,
networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
) dockerData {
dData := dockerData{
ID: task.ID,
ServiceName: serviceDockerData.Name,
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
Labels: serviceDockerData.Labels,
ExtraConf: serviceDockerData.ExtraConf,
NetworkSettings: networkSettings{},
}
if isGlobalSvc {
dData.Name = serviceDockerData.Name + "." + task.ID
}
if task.NetworksAttachments != nil {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range task.NetworksAttachments {
if networkService, present := networkMap[virtualIP.Network.ID]; present {
if len(virtualIP.Addresses) > 0 {
// Not sure about this next loop - when would a task have multiple IP's for the same network?
for _, addr := range virtualIP.Addresses {
ip, _, _ := net.ParseCIDR(addr)
network := &networkData{
ID: virtualIP.Network.ID,
Name: networkService.Name,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
}
} else {
log.Ctx(ctx).Debug().Msgf("No IP addresses found for network %s", virtualIP.Network.ID)
}
}
}
}
return dData
}

View file

@ -0,0 +1,193 @@
package docker
import (
"context"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
dockertypes "github.com/docker/docker/api/types"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
)
// DockerAPIVersion is a constant holding the version of the Provider API traefik will use.
const DockerAPIVersion = "1.24"
const dockerName = "docker"
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
Shared `yaml:",inline" export:"true"`
ClientConfig `yaml:",inline" export:"true"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.ExposedByDefault = true
p.Endpoint = "unix:///var/run/docker.sock"
p.DefaultRule = DefaultTemplateRule
}
// Init the provider.
func (p *Provider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
if err != nil {
return fmt.Errorf("error while parsing default rule: %w", err)
}
p.defaultRuleTpl = defaultRuleTpl
return nil
}
func (p *Provider) createClient(ctx context.Context) (*client.Client, error) {
p.ClientConfig.apiVersion = DockerAPIVersion
return createClient(ctx, p.ClientConfig)
}
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
pool.GoCtx(func(routineCtx context.Context) {
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, dockerName).Logger()
ctxLog := logger.WithContext(routineCtx)
operation := func() error {
var err error
ctx, cancel := context.WithCancel(ctxLog)
defer cancel()
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, dockerName).Logger().WithContext(ctx)
dockerClient, err := p.createClient(ctxLog)
if err != nil {
logger.Error().Err(err).Msg("Failed to create Docker API client")
return err
}
defer func() { _ = dockerClient.Close() }()
builder := NewDynConfBuilder(p.Shared, dockerClient)
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
return err
}
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
dockerDataList, err := p.listContainers(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list containers for docker")
return err
}
configuration := builder.build(ctxLog, dockerDataList)
configurationChan <- dynamic.Message{
ProviderName: dockerName,
Configuration: configuration,
}
if p.Watch {
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
startStopHandle := func(m eventtypes.Message) {
logger.Debug().Msgf("Provider event received %+v", m)
containers, err := p.listContainers(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list containers for docker")
// Call cancel to get out of the monitor
return
}
configuration := builder.build(ctx, containers)
if configuration != nil {
message := dynamic.Message{
ProviderName: dockerName,
Configuration: configuration,
}
select {
case configurationChan <- message:
case <-ctx.Done():
}
}
}
eventsc, errc := dockerClient.Events(ctx, options)
for {
select {
case event := <-eventsc:
if event.Action == "start" ||
event.Action == "die" ||
strings.HasPrefix(event.Action, "health_status") {
startStopHandle(event)
}
case err := <-errc:
if errors.Is(err, io.EOF) {
logger.Debug().Msg("Provider event stream closed")
}
return err
case <-ctx.Done():
return nil
}
}
}
return nil
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})
return nil
}
func (p *Provider) listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
if err != nil {
return nil, err
}
var inspectedContainers []dockerData
// get inspect containers
for _, container := range containerList {
dData := inspectContainers(ctx, dockerClient, container.ID)
if len(dData.Name) == 0 {
continue
}
extraConf, err := p.extractLabels(dData)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue
}
dData.ExtraConf = extraConf
inspectedContainers = append(inspectedContainers, dData)
}
return inspectedContainers, nil
}

View file

@ -0,0 +1,332 @@
package docker
import (
"context"
"fmt"
"net"
"strconv"
"time"
"github.com/cenkalti/backoff/v4"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/rs/zerolog/log"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
)
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use.
const SwarmAPIVersion = "1.24"
const swarmName = "swarm"
var _ provider.Provider = (*SwarmProvider)(nil)
// SwarmProvider holds configurations of the provider.
type SwarmProvider struct {
Shared `yaml:",inline" export:"true"`
ClientConfig `yaml:",inline" export:"true"`
RefreshSeconds ptypes.Duration `description:"Polling interval for swarm mode." json:"refreshSeconds,omitempty" toml:"refreshSeconds,omitempty" yaml:"refreshSeconds,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (p *SwarmProvider) SetDefaults() {
p.Watch = true
p.ExposedByDefault = true
p.Endpoint = "unix:///var/run/docker.sock"
p.RefreshSeconds = ptypes.Duration(15 * time.Second)
p.DefaultRule = DefaultTemplateRule
}
// Init the provider.
func (p *SwarmProvider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
if err != nil {
return fmt.Errorf("error while parsing default rule: %w", err)
}
p.defaultRuleTpl = defaultRuleTpl
return nil
}
func (p *SwarmProvider) createClient(ctx context.Context) (*client.Client, error) {
p.ClientConfig.apiVersion = SwarmAPIVersion
return createClient(ctx, p.ClientConfig)
}
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
func (p *SwarmProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
pool.GoCtx(func(routineCtx context.Context) {
logger := log.Ctx(routineCtx).With().Str(logs.ProviderName, swarmName).Logger()
ctxLog := logger.WithContext(routineCtx)
operation := func() error {
var err error
ctx, cancel := context.WithCancel(ctxLog)
defer cancel()
ctx = log.Ctx(ctx).With().Str(logs.ProviderName, swarmName).Logger().WithContext(ctx)
dockerClient, err := p.createClient(ctx)
if err != nil {
logger.Error().Err(err).Msg("Failed to create Docker API client")
return err
}
defer func() { _ = dockerClient.Close() }()
builder := NewDynConfBuilder(p.Shared, dockerClient)
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
logger.Error().Err(err).Msg("Failed to retrieve information of the docker client and server host")
return err
}
logger.Debug().Msgf("Provider connection established with docker %s (API %s)", serverVersion.Version, serverVersion.APIVersion)
dockerDataList, err := p.listServices(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
return err
}
configuration := builder.build(ctxLog, dockerDataList)
configurationChan <- dynamic.Message{
ProviderName: swarmName,
Configuration: configuration,
}
if p.Watch {
errChan := make(chan error)
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
ticker := time.NewTicker(time.Duration(p.RefreshSeconds))
pool.GoCtx(func(ctx context.Context) {
logger := log.Ctx(ctx).With().Str(logs.ProviderName, swarmName).Logger()
ctx = logger.WithContext(ctx)
defer close(errChan)
for {
select {
case <-ticker.C:
services, err := p.listServices(ctx, dockerClient)
if err != nil {
logger.Error().Err(err).Msg("Failed to list services for docker swarm mode")
errChan <- err
return
}
configuration := builder.build(ctx, services)
if configuration != nil {
configurationChan <- dynamic.Message{
ProviderName: swarmName,
Configuration: configuration,
}
}
case <-ctx.Done():
ticker.Stop()
return
}
}
})
if err, ok := <-errChan; ok {
return err
}
// channel closed
}
return nil
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})
return nil
}
func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
logger := log.Ctx(ctx)
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
if err != nil {
return nil, err
}
serverVersion, err := dockerClient.ServerVersion(ctx)
if err != nil {
return nil, err
}
networkListArgs := filters.NewArgs()
// https://docs.docker.com/engine/api/v1.29/#tag/Network (Docker 17.06)
if versions.GreaterThanOrEqualTo(serverVersion.APIVersion, "1.29") {
networkListArgs.Add("scope", "swarm")
} else {
networkListArgs.Add("driver", "overlay")
}
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
if err != nil {
logger.Debug().Err(err).Msg("Failed to network inspect on client for docker")
return nil, err
}
networkMap := make(map[string]*dockertypes.NetworkResource)
for _, network := range networkList {
networkToAdd := network
networkMap[network.ID] = &networkToAdd
}
var dockerDataList []dockerData
var dockerDataListTasks []dockerData
for _, service := range serviceList {
dData, err := p.parseService(ctx, service, networkMap)
if err != nil {
logger.Error().Err(err).Msgf("Skip container %s", getServiceName(dData))
continue
}
if dData.ExtraConf.Docker.LBSwarm {
if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData)
}
} else {
isGlobalSvc := service.Spec.Mode.Global != nil
dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dData, networkMap, isGlobalSvc)
if err != nil {
logger.Warn().Err(err).Send()
} else {
dockerDataList = append(dockerDataList, dockerDataListTasks...)
}
}
}
return dockerDataList, err
}
func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) {
logger := log.Ctx(ctx)
dData := dockerData{
ID: service.ID,
ServiceName: service.Spec.Annotations.Name,
Name: service.Spec.Annotations.Name,
Labels: service.Spec.Annotations.Labels,
NetworkSettings: networkSettings{},
}
extraConf, err := p.extractLabels(dData)
if err != nil {
return dockerData{}, err
}
dData.ExtraConf = extraConf
if service.Spec.EndpointSpec != nil {
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
if dData.ExtraConf.Docker.LBSwarm {
logger.Warn().Msgf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
}
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range service.Endpoint.VirtualIPs {
networkService := networkMap[virtualIP.NetworkID]
if networkService != nil {
if len(virtualIP.Addr) > 0 {
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
network := &networkData{
Name: networkService.Name,
ID: virtualIP.NetworkID,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
} else {
logger.Debug().Msgf("No virtual IPs found in network %s", virtualIP.NetworkID)
}
} else {
logger.Debug().Msgf("Network not found, id: %s", virtualIP.NetworkID)
}
}
}
}
return dData, nil
}
func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string,
serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
) ([]dockerData, error) {
serviceIDFilter := filters.NewArgs()
serviceIDFilter.Add("service", serviceID)
serviceIDFilter.Add("desired-state", "running")
taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter})
if err != nil {
return nil, err
}
var dockerDataList []dockerData
for _, task := range taskList {
if task.Status.State != swarmtypes.TaskStateRunning {
continue
}
dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc)
if len(dData.NetworkSettings.Networks) > 0 {
dockerDataList = append(dockerDataList, dData)
}
}
return dockerDataList, err
}
func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData,
networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool,
) dockerData {
dData := dockerData{
ID: task.ID,
ServiceName: serviceDockerData.Name,
Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot),
Labels: serviceDockerData.Labels,
ExtraConf: serviceDockerData.ExtraConf,
NetworkSettings: networkSettings{},
}
if isGlobalSvc {
dData.Name = serviceDockerData.Name + "." + task.ID
}
if task.NetworksAttachments != nil {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for _, virtualIP := range task.NetworksAttachments {
if networkService, present := networkMap[virtualIP.Network.ID]; present {
if len(virtualIP.Addresses) > 0 {
// Not sure about this next loop - when would a task have multiple IP's for the same network?
for _, addr := range virtualIP.Addresses {
ip, _, _ := net.ParseCIDR(addr)
network := &networkData{
ID: virtualIP.Network.ID,
Name: networkService.Name,
Addr: ip.String(),
}
dData.NetworkSettings.Networks[network.Name] = network
}
} else {
log.Ctx(ctx).Debug().Msgf("No IP addresses found for network %s", virtualIP.Network.ID)
}
}
}
}
return dData
}

View file

@ -0,0 +1,49 @@
package docker
import (
"context"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client"
)
type fakeTasksClient struct {
dockerclient.APIClient
tasks []swarm.Task
container dockertypes.ContainerJSON
err error
}
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}
func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) {
return c.container, c.err
}
type fakeServicesClient struct {
dockerclient.APIClient
dockerVersion string
networks []dockertypes.NetworkResource
services []swarm.Service
tasks []swarm.Task
err error
}
func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) {
return c.services, c.err
}
func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) {
return dockertypes.Version{APIVersion: c.dockerVersion}, c.err
}
func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) {
return c.networks, c.err
}
func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}

View file

@ -9,26 +9,10 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
dockertypes "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type fakeTasksClient struct {
dockerclient.APIClient
tasks []swarm.Task
container dockertypes.ContainerJSON
err error
}
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}
func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) {
return c.container, c.err
}
func TestListTasks(t *testing.T) { func TestListTasks(t *testing.T) {
testCases := []struct { testCases := []struct {
service swarm.Service service swarm.Service
@ -83,7 +67,7 @@ func TestListTasks(t *testing.T) {
t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{} p := SwarmProvider{}
dockerData, err := p.parseService(context.Background(), test.service, test.networks) dockerData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)
@ -103,32 +87,7 @@ func TestListTasks(t *testing.T) {
} }
} }
type fakeServicesClient struct { func TestSwarmProvider_listServices(t *testing.T) {
dockerclient.APIClient
dockerVersion string
networks []dockertypes.NetworkResource
services []swarm.Service
tasks []swarm.Task
err error
}
func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) {
return c.services, c.err
}
func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) {
return dockertypes.Version{APIVersion: c.dockerVersion}, c.err
}
func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) {
return c.networks, c.err
}
func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}
func TestListServices(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
services []swarm.Service services []swarm.Service
@ -277,7 +236,7 @@ func TestListServices(t *testing.T) {
dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks} dockerClient := &fakeServicesClient{services: test.services, tasks: test.tasks, dockerVersion: test.dockerVersion, networks: test.networks}
p := Provider{} p := SwarmProvider{}
serviceDockerData, err := p.listServices(context.Background(), dockerClient) serviceDockerData, err := p.listServices(context.Background(), dockerClient)
assert.NoError(t, err) assert.NoError(t, err)
@ -293,7 +252,7 @@ func TestListServices(t *testing.T) {
} }
} }
func TestSwarmTaskParsing(t *testing.T) { func TestSwarmProvider_parseService_task(t *testing.T) {
testCases := []struct { testCases := []struct {
service swarm.Service service swarm.Service
tasks []swarm.Task tasks []swarm.Task
@ -396,7 +355,7 @@ func TestSwarmTaskParsing(t *testing.T) {
t.Run(strconv.Itoa(caseID), func(t *testing.T) { t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{} p := SwarmProvider{}
dData, err := p.parseService(context.Background(), test.service, test.networks) dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err) require.NoError(t, err)

View file

@ -0,0 +1,211 @@
package docker
import (
"context"
"fmt"
"net/http"
"text/template"
"time"
"github.com/docker/cli/cli/connhelper"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/docker/go-connections/sockets"
"github.com/rs/zerolog/log"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/traefik/traefik/v3/pkg/version"
)
// DefaultTemplateRule The default template for the default rule.
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
type Shared struct {
ExposedByDefault bool `description:"Expose containers by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"`
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
AllowEmptyServices bool `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
Network string `description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,omitempty" export:"true"`
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network." json:"useBindPortIP,omitempty" toml:"useBindPortIP,omitempty" yaml:"useBindPortIP,omitempty" export:"true"`
Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
defaultRuleTpl *template.Template
}
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
return dockerData{}
}
// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
// We register only container which are running
if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
return parseContainer(containerInspected)
}
return dockerData{}
}
func parseContainer(container dockertypes.ContainerJSON) dockerData {
dData := dockerData{
NetworkSettings: networkSettings{},
}
if container.ContainerJSONBase != nil {
dData.ID = container.ContainerJSONBase.ID
dData.Name = container.ContainerJSONBase.Name
dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
dData.Node = container.ContainerJSONBase.Node
if container.ContainerJSONBase.HostConfig != nil {
dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
}
if container.State != nil && container.State.Health != nil {
dData.Health = container.State.Health.Status
}
}
if container.Config != nil && container.Config.Labels != nil {
dData.Labels = container.Config.Labels
}
if container.NetworkSettings != nil {
if container.NetworkSettings.Ports != nil {
dData.NetworkSettings.Ports = container.NetworkSettings.Ports
}
if container.NetworkSettings.Networks != nil {
dData.NetworkSettings.Networks = make(map[string]*networkData)
for name, containerNetwork := range container.NetworkSettings.Networks {
addr := containerNetwork.IPAddress
if addr == "" {
addr = containerNetwork.GlobalIPv6Address
}
dData.NetworkSettings.Networks[name] = &networkData{
ID: containerNetwork.NetworkID,
Name: name,
Addr: addr,
}
}
}
}
return dData
}
type ClientConfig struct {
apiVersion string
Endpoint string `description:"Docker server endpoint. Can be a TCP or a Unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
}
func createClient(ctx context.Context, cfg ClientConfig) (*client.Client, error) {
opts, err := getClientOpts(ctx, cfg)
if err != nil {
return nil, err
}
httpHeaders := map[string]string{
"User-Agent": "Traefik " + version.Version,
}
opts = append(opts,
client.WithHTTPHeaders(httpHeaders),
client.WithVersion(cfg.apiVersion))
return client.NewClientWithOpts(opts...)
}
func getClientOpts(ctx context.Context, cfg ClientConfig) ([]client.Opt, error) {
helper, err := connhelper.GetConnectionHelper(cfg.Endpoint)
if err != nil {
return nil, err
}
// SSH
if helper != nil {
// https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: helper.Dialer,
},
}
return []client.Opt{
client.WithHTTPClient(httpClient),
client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error
client.WithDialContext(helper.Dialer),
}, nil
}
opts := []client.Opt{
client.WithHost(cfg.Endpoint),
client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
}
if cfg.TLS != nil {
conf, err := cfg.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
}
hostURL, err := client.ParseHostURL(cfg.Endpoint)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: conf,
}
if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil {
return nil, err
}
opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(cfg.HTTPClientTimeout)}))
}
return opts, nil
}
func getPort(container dockerData, serverPort string) string {
if len(serverPort) > 0 {
return serverPort
}
var ports []nat.Port
for port := range container.NetworkSettings.Ports {
ports = append(ports, port)
}
less := func(i, j nat.Port) bool {
return i.Int() < j.Int()
}
nat.Sort(ports, less)
if len(ports) > 0 {
min := ports[0]
return min.Port()
}
return ""
}
func getServiceName(container dockerData) string {
serviceName := container.ServiceName
if values, err := getStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject]
}
return provider.Normalize(serviceName)
}

View file

@ -23,7 +23,7 @@ type specificConfiguration struct {
LBSwarm bool LBSwarm bool
} }
func (p *Provider) getConfiguration(container dockerData) (configuration, error) { func (p *Shared) extractLabels(container dockerData) (configuration, error) {
conf := configuration{ conf := configuration{
Enable: p.ExposedByDefault, Enable: p.ExposedByDefault,
Docker: specificConfiguration{ Docker: specificConfiguration{

View file

@ -0,0 +1,112 @@
package docker
import (
"context"
"strconv"
"testing"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_getPort_docker(t *testing.T) {
testCases := []struct {
desc string
container docker.ContainerJSON
serverPort string
expected string
}{
{
desc: "no binding, no server port label",
container: containerJSON(name("foo")),
expected: "",
},
{
desc: "binding, no server port label",
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
})),
expected: "80",
},
{
desc: "binding, multiple ports, no server port label",
container: containerJSON(ports(nat.PortMap{
"80/tcp": {},
"443/tcp": {},
})),
expected: "80",
},
{
desc: "no binding, server port label",
container: containerJSON(),
serverPort: "8080",
expected: "8080",
},
{
desc: "binding, server port label",
container: containerJSON(
ports(nat.PortMap{
"80/tcp": {},
})),
serverPort: "8080",
expected: "8080",
},
{
desc: "binding, multiple ports, server port label",
container: containerJSON(ports(nat.PortMap{
"8080/tcp": {},
"80/tcp": {},
})),
serverPort: "8080",
expected: "8080",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dData := parseContainer(test.container)
actual := getPort(dData, test.serverPort)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_getPort_swarm(t *testing.T) {
testCases := []struct {
service swarm.Service
serverPort string
networks map[string]*docker.NetworkResource
expected string
}{
{
service: swarmService(
withEndpointSpec(modeDNSSR),
),
networks: map[string]*docker.NetworkResource{},
serverPort: "8080",
expected: "8080",
},
}
for serviceID, test := range testCases {
test := test
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
p := SwarmProvider{}
dData, err := p.parseService(context.Background(), test.service, test.networks)
require.NoError(t, err)
actual := getPort(dData, test.serverPort)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -39,7 +39,7 @@ import (
"github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/types"
) )
var updateExpected = flag.Bool("update_expected", true, "Update expected files in fixtures") var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
var fullDynConf *dynamic.Configuration var fullDynConf *dynamic.Configuration
@ -591,22 +591,46 @@ func TestDo_staticConfiguration(t *testing.T) {
} }
config.Providers.Docker = &docker.Provider{ config.Providers.Docker = &docker.Provider{
Shared: docker.Shared{
ExposedByDefault: true,
Constraints: `Label("foo", "bar")`, Constraints: `Label("foo", "bar")`,
AllowEmptyServices: true,
Network: "MyNetwork",
UseBindPortIP: true,
Watch: true, Watch: true,
Endpoint: "MyEndPoint",
DefaultRule: "PathPrefix(`/`)", DefaultRule: "PathPrefix(`/`)",
TLS: &types.ClientTLS{ },
ClientConfig: docker.ClientConfig{
Endpoint: "MyEndPoint", TLS: &types.ClientTLS{
CA: "myCa", CA: "myCa",
Cert: "mycert.pem", Cert: "mycert.pem",
Key: "mycert.key", Key: "mycert.key",
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
ExposedByDefault: true,
UseBindPortIP: true,
SwarmMode: true,
Network: "MyNetwork",
SwarmModeRefreshSeconds: 42,
HTTPClientTimeout: 42, HTTPClientTimeout: 42,
},
}
config.Providers.Swarm = &docker.SwarmProvider{
Shared: docker.Shared{
ExposedByDefault: true,
Constraints: `Label("foo", "bar")`,
AllowEmptyServices: true,
Network: "MyNetwork",
UseBindPortIP: true,
Watch: true,
DefaultRule: "PathPrefix(`/`)",
},
ClientConfig: docker.ClientConfig{
Endpoint: "MyEndPoint", TLS: &types.ClientTLS{
CA: "myCa",
Cert: "mycert.pem",
Key: "mycert.key",
InsecureSkipVerify: true,
},
HTTPClientTimeout: 42,
},
RefreshSeconds: 42,
} }
config.Providers.KubernetesIngress = &ingress.Provider{ config.Providers.KubernetesIngress = &ingress.Provider{

View file

@ -89,23 +89,40 @@
"providers": { "providers": {
"providersThrottleDuration": "1m51s", "providersThrottleDuration": "1m51s",
"docker": { "docker": {
"exposedByDefault": true,
"constraints": "Label(\"foo\", \"bar\")", "constraints": "Label(\"foo\", \"bar\")",
"allowEmptyServices": true,
"network": "MyNetwork",
"useBindPortIP": true,
"watch": true, "watch": true,
"endpoint": "xxxx",
"defaultRule": "xxxx", "defaultRule": "xxxx",
"endpoint": "xxxx",
"tls": { "tls": {
"ca": "xxxx", "ca": "xxxx",
"cert": "xxxx", "cert": "xxxx",
"key": "xxxx", "key": "xxxx",
"insecureSkipVerify": true "insecureSkipVerify": true
}, },
"exposedByDefault": true,
"useBindPortIP": true,
"swarmMode": true,
"network": "MyNetwork",
"swarmModeRefreshSeconds": "42ns",
"httpClientTimeout": "42ns" "httpClientTimeout": "42ns"
}, },
"swarm": {
"exposedByDefault": true,
"constraints": "Label(\"foo\", \"bar\")",
"allowEmptyServices": true,
"network": "MyNetwork",
"useBindPortIP": true,
"watch": true,
"defaultRule": "xxxx",
"endpoint": "xxxx",
"tls": {
"ca": "xxxx",
"cert": "xxxx",
"key": "xxxx",
"insecureSkipVerify": true
},
"httpClientTimeout": "42ns",
"refreshSeconds": "42ns"
},
"file": { "file": {
"directory": "file Directory", "directory": "file Directory",
"watch": true, "watch": true,

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52">
<g fill="none" fill-rule="evenodd">
<circle cx="26" cy="26" r="26" fill="#007BFF"/>
<path fill="#FFF" fill-rule="nonzero" d="M22.749 13v3.94h-8.125v3.939h-4.062v3.94H7.414a.785.785 0 0 0-.787.64 8.97 8.97 0 0 0-.127 1.526c0 .55.054 1.15.152 1.748.93-.215 2.634-.539 2.336-1.6 1.603 1.8 5.449 1.255 6.423.369 1.089 1.53 7.43.945 7.87-.246 1.365 1.55 5.596 1.55 6.957 0 .441 1.19 6.757 1.775 7.845.246.346.314 1.066.575 1.904.714.283-.52.543-1.043.787-1.6 5.16-.062 6.274-3.66 6.322-3.817.089-.301-.032-.612-.28-.812-.085-.071-1.99-1.57-5.077-1.059-.866-2.736-3.097-3.982-3.199-4.038a.826.826 0 0 0-.914.074c-.082.065-2.002 1.643-1.701 4.875.076.813.295 1.53.635 2.167-.667.36-1.812.812-3.656.812h-.406v-3.94h-4.063V13H22.75zm17.238 15.216c-.098.185-.225.363-.33.542h7.337c-.882-.216-2.777-.505-2.462-1.625-1.038 1.163-3.009 1.335-4.545 1.083zm-.33.542H6.652a12.07 12.07 0 0 0 1.219 3.545c4.411 1.006 9.041-.526 9.089-.542a.8.8 0 0 1 1.016.493.78.78 0 0 1-.483 1.01c-.155.052-2.945.96-6.372.96-.68 0-1.394-.037-2.108-.124 2.133 2.743 5.748 4.9 11.298 4.9 8.778 0 15.528-3.616 19.346-10.242zm-33.005 0c-.003-.013.004-.013 0-.025-.054.012-.104.012-.152.025h.152zm17.721-14.182h2.438v2.363h-2.438v-2.363zm-8.124 3.94h2.437v2.363H16.25v-2.364zm4.062 0h2.438v2.363H20.31v-2.364zm4.062 0h2.438v2.363h-2.438v-2.364zm-12.186 3.939h2.437v2.363h-2.437v-2.363zm4.062 0h2.437v2.363H16.25v-2.363zm4.062 0h2.438v2.363H20.31v-2.363zm4.062 0h2.438v2.363h-2.438v-2.363zm4.062 0h2.438v2.363h-2.438v-2.363zM19.5 30.333c.105 0 .212.016.304.05-.098.055-.177.15-.177.27 0 .179.146.345.33.345a.367.367 0 0 0 .304-.172.7.7 0 0 1 .051.295.802.802 0 0 1-.812.788.802.802 0 0 1-.813-.788c0-.434.365-.788.813-.788z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB