Repository: traefik/traefik (language: go)

Issue:
Move docker.swarm to standalone Swarm provider
<!--
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.

The issue tracker is for reporting bugs and feature requests only.
For end-user related support questions, please refer to one of the following:

- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
- the Traefik community Slack channel: https://slack.traefik.io

-->


### Do you want to request a *feature* or report a *bug*?

Feature

### What did you expect to see?

<!--

HOW TO WRITE A GOOD ISSUE?

- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that you’re facing.
- Remain clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown

-->
I think that swarm option should be moved to standalone Swarm provider, because it's not possible to use proxy with services deployed with docker stack command while you can still run containers on the same host outside outside of swarm under the same docker network (because of missing priviledge mode in swarm)


Discussion / hints:
Hi @bauer01,

 Thank you for your interest in Traefik.

So far the current architecture favors a solution involving a single Traefik per provider of same type, instead of your choice of a single traefik for several providers of same type.

We are not currently planning to change this architecture as of today until a tremendous interest for this feature.


Hi @jbdoumenjou ,
thank you for your reply.

No problem with that. And what about to implement logic that enable traefik routing for docker containers outside of swarm if swarmmode enabled? It shouldn't be a big deal ...
Hi @jbdoumenjou,

I think @bauer01 has a good point.

In some situations you sadly can't run a container as a swarm service (e.g. dind). You'll then spin up a docker container outside of swarm. 

Via the `--attachable` flag of networks, all containers are able to connect to the same traefik network.
Lables like `traefik.enabled` can of course be applied to containers inside and outside of swarm.
In this scenario all containers run on the same docker host.

So traefik in theory should be possible to consider all of them?

How about adding an additional provider option `providers.docker.combinedMode` standalone and swarm mode  (Optional, Default=false)?

This would ensure backwards compatibility and give the option to let traefik consider containers inside and outside swarm.
What are your thoughts on this?

Thanks!
Docker and Swarm use 2 completely different API, so it's not possible to "combine" them.
@ldez 
Yes it's true that the APIs are different.
But it's also true, that the connection to both APIs already has been implemented.
So I doubt it's impossible:

Depending on `p.SwarmMode` either services or containers are retrieved and passed to the ~generic `p.buildConfiguration(ctx, data)`.
The parameter `data` can already be either containers or services.

However, looking at the code I don't think this is a quick fix. 
For this to work, one would need to make a significant amount of changes to `docker.go`.
To name only a few:
- adding new config option incl. the logic considering all effects of the new option on the old options
- consider using two instead of one client instance
- running two blocking for loops in parallel instead of running a single one
- merging container and service data based config
- take care not to mix up standalone-containers and swarm-containers
- tests, documentation, ...


the real solution is just to split the Docker provider.

Fix patch:
diff --git a/docs/content/contributing/data-collection.md b/docs/content/contributing/data-collection.md
index f68f329ccb..645170b588 100644
--- a/docs/content/contributing/data-collection.md
+++ b/docs/content/contributing/data-collection.md
@@ -66,7 +66,6 @@ providers:
   docker:
     endpoint: "tcp://10.10.10.10:2375"
     exposedByDefault: true
-    swarmMode: true
 
     tls:
       ca: dockerCA
@@ -86,7 +85,6 @@ providers:
   docker:
     endpoint: "xxxx"
     exposedByDefault: true
-    swarmMode: true
 
     tls:
       ca: xxxx
diff --git a/docs/content/https/include-acme-multiple-domains-example.md b/docs/content/https/include-acme-multiple-domains-example.md
index e60d08f1cf..a904b8950e 100644
--- a/docs/content/https/include-acme-multiple-domains-example.md
+++ b/docs/content/https/include-acme-multiple-domains-example.md
@@ -1,5 +1,5 @@
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 ## Dynamic configuration
 labels:
   - traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
diff --git a/docs/content/https/include-acme-multiple-domains-from-rule-example.md b/docs/content/https/include-acme-multiple-domains-from-rule-example.md
index 1052228c3f..4e27bb84d6 100644
--- a/docs/content/https/include-acme-multiple-domains-from-rule-example.md
+++ b/docs/content/https/include-acme-multiple-domains-from-rule-example.md
@@ -1,5 +1,5 @@
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 ## Dynamic configuration
 labels:
   - traefik.http.routers.blog.rule=(Host(`example.com`) && Path(`/blog`)) || Host(`blog.example.org`)
diff --git a/docs/content/https/include-acme-single-domain-example.md b/docs/content/https/include-acme-single-domain-example.md
index f6fad9af9c..7408be5d2a 100644
--- a/docs/content/https/include-acme-single-domain-example.md
+++ b/docs/content/https/include-acme-single-domain-example.md
@@ -1,5 +1,5 @@
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 ## Dynamic configuration
 labels:
   - traefik.http.routers.blog.rule=Host(`example.com`) && Path(`/blog`)
diff --git a/docs/content/https/tailscale.md b/docs/content/https/tailscale.md
index 6cb2f3e6c9..d49c314c5d 100644
--- a/docs/content/https/tailscale.md
+++ b/docs/content/https/tailscale.md
@@ -87,7 +87,7 @@ A certificate resolver requests certificates for a set of domain names inferred
 
 !!! example "Domain from Router's Rule Example"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     ## Dynamic configuration
     labels:
       - 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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     ## Dynamic configuration
     labels:
       - traefik.http.routers.blog.rule=Path(`/metrics`)
diff --git a/docs/content/https/tls.md b/docs/content/https/tls.md
index 296e9b484f..b7321413ce 100644
--- a/docs/content/https/tls.md
+++ b/docs/content/https/tls.md
@@ -211,7 +211,7 @@ spec:
         - bar.example.org
 ```
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 ## Dynamic configuration
 labels:
   - "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver"
diff --git a/docs/content/middlewares/http/addprefix.md b/docs/content/middlewares/http/addprefix.md
index e6f458f31c..011a51f2e3 100644
--- a/docs/content/middlewares/http/addprefix.md
+++ b/docs/content/middlewares/http/addprefix.md
@@ -14,7 +14,7 @@ The AddPrefix middleware updates the path of a request before forwarding it.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Prefixing with /foo
 labels:
   - "traefik.http.middlewares.add-foo.addprefix.prefix=/foo"
diff --git a/docs/content/middlewares/http/basicauth.md b/docs/content/middlewares/http/basicauth.md
index d5118f855d..5c77aad193 100644
--- a/docs/content/middlewares/http/basicauth.md
+++ b/docs/content/middlewares/http/basicauth.md
@@ -14,7 +14,7 @@ The BasicAuth middleware restricts access to your services to known users.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Declaring the user list
 #
 # 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.
     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
 #
 # 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`.
     - 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:
   - "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`.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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`.)
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-auth.basicauth.removeheader=true"
 ```
diff --git a/docs/content/middlewares/http/buffering.md b/docs/content/middlewares/http/buffering.md
index 85c21a62bb..27dc603823 100644
--- a/docs/content/middlewares/http/buffering.md
+++ b/docs/content/middlewares/http/buffering.md
@@ -18,7 +18,7 @@ This can help services avoid large amounts of data (`multipart/form-data` for ex
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Sets the maximum request body to 2MB
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       - "traefik.http.middlewares.limit.buffering.retryExpression=IsNetworkError() && Attempts() < 2"
     ```
diff --git a/docs/content/middlewares/http/chain.md b/docs/content/middlewares/http/chain.md
index de00744260..caf1a84c9a 100644
--- a/docs/content/middlewares/http/chain.md
+++ b/docs/content/middlewares/http/chain.md
@@ -17,7 +17,7 @@ It makes reusing the same groups easier.
 
 Below is an example of a Chain containing `AllowList`, `BasicAuth`, and `RedirectScheme`.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.routers.router1.service=service1"
   - "traefik.http.routers.router1.middlewares=secured"
diff --git a/docs/content/middlewares/http/circuitbreaker.md b/docs/content/middlewares/http/circuitbreaker.md
index 6edaedbe92..be7b1422d2 100644
--- a/docs/content/middlewares/http/circuitbreaker.md
+++ b/docs/content/middlewares/http/circuitbreaker.md
@@ -30,7 +30,7 @@ To assess if your system is healthy, the circuit breaker constantly monitors the
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Latency Check
 labels:
   - "traefik.http.middlewares.latency-check.circuitbreaker.expression=LatencyAtQuantileMS(50.0) > 100"
diff --git a/docs/content/middlewares/http/compress.md b/docs/content/middlewares/http/compress.md
index 4618fb2457..eec8e73b08 100644
--- a/docs/content/middlewares/http/compress.md
+++ b/docs/content/middlewares/http/compress.md
@@ -15,7 +15,7 @@ The activation of compression, and the compression method choice rely (among oth
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Enable compression
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-compress.compress.minresponsebodybytes=1200"
 ```
diff --git a/docs/content/middlewares/http/contenttype.md b/docs/content/middlewares/http/contenttype.md
index c4d78a3592..f08bccb391 100644
--- a/docs/content/middlewares/http/contenttype.md
+++ b/docs/content/middlewares/http/contenttype.md
@@ -18,7 +18,7 @@ when it is not set by the backend.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Enable auto-detection
 labels:
   - "traefik.http.middlewares.autodetect.contenttype=true"
diff --git a/docs/content/middlewares/http/digestauth.md b/docs/content/middlewares/http/digestauth.md
index dcd9ca2840..72b868c055 100644
--- a/docs/content/middlewares/http/digestauth.md
+++ b/docs/content/middlewares/http/digestauth.md
@@ -14,7 +14,7 @@ The DigestAuth middleware restricts access to your services to known users.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Declaring the user list
 labels:
   - "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`.
     - 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:
   - "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`.
     - 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:
   - "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`.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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`.)
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-auth.digestauth.removeheader=true"
 ```
diff --git a/docs/content/middlewares/http/errorpages.md b/docs/content/middlewares/http/errorpages.md
index 0f8b1798ac..9456bf9782 100644
--- a/docs/content/middlewares/http/errorpages.md
+++ b/docs/content/middlewares/http/errorpages.md
@@ -18,7 +18,7 @@ The Errors middleware returns a custom page in lieu of the default, according to
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Dynamic Custom Error Page for 5XX Status Code
 labels:
   - "traefik.http.middlewares.test-errors.errors.status=500-599"
diff --git a/docs/content/middlewares/http/forwardauth.md b/docs/content/middlewares/http/forwardauth.md
index 4738747b27..6b69c620b2 100644
--- a/docs/content/middlewares/http/forwardauth.md
+++ b/docs/content/middlewares/http/forwardauth.md
@@ -16,7 +16,7 @@ Otherwise, the response from the authentication server is returned.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Forward authentication to example.com
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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
 forwarded request, replacing any existing conflicting headers.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 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:
   - "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.
 If not set or empty then all request headers are passed.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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,
 it defaults to the system bundle.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 When using this option, setting the `key` option is required.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
   - "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.
 When using this option, setting the `cert` option is required.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert"
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true"
 ```
diff --git a/docs/content/middlewares/http/grpcweb.md b/docs/content/middlewares/http/grpcweb.md
index 8b14214a24..35b4a3ddb8 100644
--- a/docs/content/middlewares/http/grpcweb.md
+++ b/docs/content/middlewares/http/grpcweb.md
@@ -17,7 +17,7 @@ The GrpcWeb middleware converts gRPC Web requests to HTTP/2 gRPC requests before
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-grpcweb.grpcweb.allowOrigins=*"
 ```
diff --git a/docs/content/middlewares/http/headers.md b/docs/content/middlewares/http/headers.md
index f0dc6e7c2c..4c30b349ef 100644
--- a/docs/content/middlewares/http/headers.md
+++ b/docs/content/middlewares/http/headers.md
@@ -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
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.testHeader.headers.customrequestheaders.X-Script-Name=test"
   - "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,
 and responses are stripped of their `X-Custom-Response-Header` header.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.testheader.headers.customrequestheaders.X-Script-Name=test"
   - "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.
 This functionality makes it possible to easily use security features by adding headers.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.testHeader.headers.framedeny=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,
 and should not be used as is for production.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.testheader.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
   - "traefik.http.middlewares.testheader.headers.accesscontrolallowheaders=*"
diff --git a/docs/content/middlewares/http/inflightreq.md b/docs/content/middlewares/http/inflightreq.md
index b7eefd84d0..e7b7ef695b 100644
--- a/docs/content/middlewares/http/inflightreq.md
+++ b/docs/content/middlewares/http/inflightreq.md
@@ -14,7 +14,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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 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:
   - "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"` | `5`     | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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"`                   | `"10.0.0.1,11.0.0.1"` | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requestheadername=username"
 ```
@@ -262,7 +262,7 @@ http:
 
 Whether to consider the request host as the source.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
 ```
diff --git a/docs/content/middlewares/http/ipallowlist.md b/docs/content/middlewares/http/ipallowlist.md
index 8892b4fcd0..d62e253bbe 100644
--- a/docs/content/middlewares/http/ipallowlist.md
+++ b/docs/content/middlewares/http/ipallowlist.md
@@ -12,7 +12,7 @@ IPAllowList accepts / refuses requests based on the client IP.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Accepts request from defined IP
 labels:
   - "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"` | `5`     | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Allowlisting Based on `X-Forwarded-For` with `depth=2`
 labels:
   - "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"`                   | `"10.0.0.1,11.0.0.1"` | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Exclude from `X-Forwarded-For`
 labels:
     - "traefik.http.middlewares.test-ipallowlist.ipallowlist.ipstrategy.excludedips=127.0.0.1/32, 192.168.1.7"
diff --git a/docs/content/middlewares/http/overview.md b/docs/content/middlewares/http/overview.md
index 500e5d38d6..17b2ab1cff 100644
--- a/docs/content/middlewares/http/overview.md
+++ b/docs/content/middlewares/http/overview.md
@@ -12,7 +12,7 @@ Controlling connections
 
 ## Configuration Example
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # As a Docker Label
 whoami:
   #  A container that exposes an API to show its IP address
diff --git a/docs/content/middlewares/http/passtlsclientcert.md b/docs/content/middlewares/http/passtlsclientcert.md
index 92c5cc83b5..8f74647d6e 100644
--- a/docs/content/middlewares/http/passtlsclientcert.md
+++ b/docs/content/middlewares/http/passtlsclientcert.md
@@ -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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Pass the pem in the `X-Forwarded-Tls-Client-Cert` header.
 labels:
   - "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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     # Pass all the available info in the `X-Forwarded-Tls-Client-Cert-Info` header
     labels:
       - "traefik.http.middlewares.test-passtlsclientcert.passtlsclientcert.info.notafter=true"
diff --git a/docs/content/middlewares/http/ratelimit.md b/docs/content/middlewares/http/ratelimit.md
index 9ae94a31a8..2359b17962 100644
--- a/docs/content/middlewares/http/ratelimit.md
+++ b/docs/content/middlewares/http/ratelimit.md
@@ -14,7 +14,7 @@ It is based on a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) impl
 
 ## Configuration Example
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Here, an average of 100 requests per second is allowed.
 # In addition, a burst of 50 requests is allowed.
 labels:
@@ -73,7 +73,7 @@ It defaults to `0`, which means no rate limiting.
 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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # 100 reqs/s
 labels:
   - "traefik.http.middlewares.test-ratelimit.ratelimit.average=100"
@@ -121,7 +121,7 @@ r = average / period
 
 It defaults to `1` second.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # 6 reqs/minute
 labels:
   - "traefik.http.middlewares.test-ratelimit.ratelimit.average=6"
@@ -170,7 +170,7 @@ http:
 
 It defaults to `1`.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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"` | `5`     | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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"`          | `"10.0.0.1,11.0.0.1"` | `""`         |
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "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.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requestheadername=username"
 ```
@@ -399,7 +399,7 @@ http:
 
 Whether to consider the request host as the source.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-ratelimit.ratelimit.sourcecriterion.requesthost=true"
 ```
diff --git a/docs/content/middlewares/http/redirectregex.md b/docs/content/middlewares/http/redirectregex.md
index d2e1466735..2788a3c6f7 100644
--- a/docs/content/middlewares/http/redirectregex.md
+++ b/docs/content/middlewares/http/redirectregex.md
@@ -16,7 +16,7 @@ The RedirectRegex redirects a request using regex matching and replacement.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Redirect with domain replacement
 # Note: all dollar signs need to be doubled for escaping.
 labels:
diff --git a/docs/content/middlewares/http/redirectscheme.md b/docs/content/middlewares/http/redirectscheme.md
index e32fecfbc7..793d28b176 100644
--- a/docs/content/middlewares/http/redirectscheme.md
+++ b/docs/content/middlewares/http/redirectscheme.md
@@ -25,7 +25,7 @@ The RedirectScheme middleware redirects the request if the request scheme is dif
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Redirect to https
 labels:
   - "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
@@ -75,7 +75,7 @@ http:
 
 Set the `permanent` option to `true` to apply a permanent redirection.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Redirect to https
 labels:
   # ...
@@ -123,7 +123,7 @@ http:
 
 The `scheme` option defines the scheme of the new URL.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Redirect to https
 labels:
   - "traefik.http.middlewares.test-redirectscheme.redirectscheme.scheme=https"
@@ -166,7 +166,7 @@ http:
 
 The `port` option defines the port of the new URL.
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Redirect to https
 labels:
   # ...
diff --git a/docs/content/middlewares/http/replacepath.md b/docs/content/middlewares/http/replacepath.md
index 2c44e9f753..e7024dffee 100644
--- a/docs/content/middlewares/http/replacepath.md
+++ b/docs/content/middlewares/http/replacepath.md
@@ -16,7 +16,7 @@ Replace the path of the request URL.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Replace the path with /foo
 labels:
   - "traefik.http.middlewares.test-replacepath.replacepath.path=/foo"
diff --git a/docs/content/middlewares/http/replacepathregex.md b/docs/content/middlewares/http/replacepathregex.md
index 562d3d2f39..7b0f956d32 100644
--- a/docs/content/middlewares/http/replacepathregex.md
+++ b/docs/content/middlewares/http/replacepathregex.md
@@ -16,7 +16,7 @@ The ReplaceRegex replaces the path of a URL using regex matching and replacement
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Replace path with regex
 labels:
   - "traefik.http.middlewares.test-replacepathregex.replacepathregex.regex=^/foo/(.*)"
diff --git a/docs/content/middlewares/http/retry.md b/docs/content/middlewares/http/retry.md
index 9706ec69f4..c8ae38d228 100644
--- a/docs/content/middlewares/http/retry.md
+++ b/docs/content/middlewares/http/retry.md
@@ -18,7 +18,7 @@ The Retry middleware has an optional configuration to enable an exponential back
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Retry 4 times with exponential backoff
 labels:
   - "traefik.http.middlewares.test-retry.retry.attempts=4"
diff --git a/docs/content/middlewares/http/stripprefix.md b/docs/content/middlewares/http/stripprefix.md
index 4d0d7d567d..5da2543b27 100644
--- a/docs/content/middlewares/http/stripprefix.md
+++ b/docs/content/middlewares/http/stripprefix.md
@@ -16,7 +16,7 @@ Remove the specified prefixes from the URL path.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Strip prefix /foobar and /fiibar
 labels:
   - "traefik.http.middlewares.test-stripprefix.stripprefix.prefixes=/foobar,/fiibar"
diff --git a/docs/content/middlewares/http/stripprefixregex.md b/docs/content/middlewares/http/stripprefixregex.md
index ba61de6fca..1e8b10ee8f 100644
--- a/docs/content/middlewares/http/stripprefixregex.md
+++ b/docs/content/middlewares/http/stripprefixregex.md
@@ -12,7 +12,7 @@ Remove the matching prefixes from the URL path.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.http.middlewares.test-stripprefixregex.stripprefixregex.regex=/foo/[a-z0-9]+/[0-9]+/"
 ```
diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md
index c01423123c..ca9ae3e6bb 100644
--- a/docs/content/middlewares/overview.md
+++ b/docs/content/middlewares/overview.md
@@ -23,7 +23,7 @@ Middlewares that use the same protocol can be combined into chains to fit every
 
 ## Configuration Example
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # As a Docker Label
 whoami:
   #  A container that exposes an API to show its IP address
diff --git a/docs/content/middlewares/tcp/inflightconn.md b/docs/content/middlewares/tcp/inflightconn.md
index f9993a7f8e..38ad09f65e 100644
--- a/docs/content/middlewares/tcp/inflightconn.md
+++ b/docs/content/middlewares/tcp/inflightconn.md
@@ -7,7 +7,7 @@ To proactively prevent services from being overwhelmed with high load, the numbe
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 labels:
   - "traefik.tcp.middlewares.test-inflightconn.inflightconn.amount=10"
 ```
diff --git a/docs/content/middlewares/tcp/ipallowlist.md b/docs/content/middlewares/tcp/ipallowlist.md
index 8aa5be01c1..e8466b94e7 100644
--- a/docs/content/middlewares/tcp/ipallowlist.md
+++ b/docs/content/middlewares/tcp/ipallowlist.md
@@ -12,7 +12,7 @@ IPAllowList accepts / refuses connections based on the client IP.
 
 ## Configuration Examples
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Accepts connections from defined IP
 labels:
   - "traefik.tcp.middlewares.test-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.7"
diff --git a/docs/content/middlewares/tcp/overview.md b/docs/content/middlewares/tcp/overview.md
index ec28af85d4..5572160cb1 100644
--- a/docs/content/middlewares/tcp/overview.md
+++ b/docs/content/middlewares/tcp/overview.md
@@ -12,7 +12,7 @@ Controlling connections
 
 ## Configuration Example
 
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # As a Docker Label
 whoami:
   #  A container that exposes an API to show its IP address
diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md
index 8d93e48f3d..31cf05e8b7 100644
--- a/docs/content/migration/v1-to-v2.md
+++ b/docs/content/migration/v1-to-v2.md
@@ -38,7 +38,7 @@ Then any router can refer to an instance of the wanted middleware.
 
     !!! info "v1"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       - "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test"
       - "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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       - "traefik.http.routers.router0.rule=Host(`test.localhost`) && PathPrefix(`/test`)"
       - "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
     ```
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       # myTLSOptions must be defined by another provider, in this instance in the File Provider.
       # see the cross provider section
@@ -428,7 +428,7 @@ To apply a redirection:
 
     !!! info "v2"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       traefik.http.routers.app.rule: Host(`example.net`)
       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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       - "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"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     labels:
       - "traefik.http.routers.admin.rule=Host(`example.org`) && PathPrefix(`/admin`)"
       - "traefik.http.routers.admin.middlewares=admin-stripprefix"
@@ -1044,7 +1044,7 @@ To activate the dashboard, you can either:
 
     !!! info "v2"
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     # dynamic configuration
     labels:
       - "traefik.http.routers.api.rule=Host(`traefik.docker.localhost`)"
diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md
index 4792467b0a..6de93ca9ca 100644
--- a/docs/content/migration/v2-to-v3.md
+++ b/docs/content/migration/v2-to-v3.md
@@ -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. 
 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)
diff --git a/docs/content/operations/include-api-examples.md b/docs/content/operations/include-api-examples.md
index d98db97a9b..1f5418bbbb 100644
--- a/docs/content/operations/include-api-examples.md
+++ b/docs/content/operations/include-api-examples.md
@@ -1,4 +1,4 @@
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Dynamic Configuration
 labels:
   - "traefik.http.routers.api.rule=Host(`traefik.example.com`)"
diff --git a/docs/content/operations/include-dashboard-examples.md b/docs/content/operations/include-dashboard-examples.md
index 5965d70708..6ee1847096 100644
--- a/docs/content/operations/include-dashboard-examples.md
+++ b/docs/content/operations/include-dashboard-examples.md
@@ -1,4 +1,4 @@
-```yaml tab="Docker"
+```yaml tab="Docker & Swarm"
 # Dynamic Configuration
 labels:
   - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md
index 177a397026..3d6ce6b725 100644
--- a/docs/content/providers/docker.md
+++ b/docs/content/providers/docker.md
@@ -12,8 +12,7 @@ A Story of Labels & Containers
 
 Attach labels to your containers and let Traefik do the rest!
 
-Traefik works with both [Docker (standalone) Engine](https://docs.docker.com/engine/)
-and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
+This provider works with [Docker (standalone) Engine](https://docs.docker.com/engine/).
 
 !!! 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`)
     ```
 
-??? 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
 
 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,
   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`
-  (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
 
 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`
@@ -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/)
         - 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.
-          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).
         - 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/)
         - [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
 
 ### `endpoint`
 
 _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"
 
@@ -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`
 
 _Optional, Default=0_
diff --git a/docs/content/providers/overview.md b/docs/content/providers/overview.md
index fbb1335895..02d2ec22f9 100644
--- a/docs/content/providers/overview.md
+++ b/docs/content/providers/overview.md
@@ -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:
 
-    ```yaml tab="Docker"
+    ```yaml tab="Docker & Swarm"
     your-container: #
       image: your-docker-image
 
diff --git a/docs/content/providers/swarm.md b/docs/content/providers/swarm.md
new file mode 100644
index 0000000000..96c987c876
--- /dev/null
+++ b/docs/content/providers/swarm.md
@@ -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!}
diff --git a/docs/content/reference/dynamic-configuration/docker.md b/docs/content/reference/dynamic-configuration/docker.md
index 1d56559bb2..3b9163a532 100644
--- a/docs/content/reference/dynamic-configuration/docker.md
+++ b/docs/content/reference/dynamic-configuration/docker.md
@@ -8,7 +8,7 @@ description: "Reference dynamic configuration with Docker labels in Traefik Prox
 Dynamic configuration with Docker Labels
 {: .subtitle }
 
-The labels are case insensitive.
+The labels are case-insensitive.
 
 ```yaml
 labels:
diff --git a/docs/content/reference/dynamic-configuration/docker.yml b/docs/content/reference/dynamic-configuration/docker.yml
index 6f9e1c62f5..097de499dc 100644
--- a/docs/content/reference/dynamic-configuration/docker.yml
+++ b/docs/content/reference/dynamic-configuration/docker.yml
@@ -1,3 +1,2 @@
 - "traefik.enable=true"
 - "traefik.docker.network=foobar"
-- "traefik.docker.lbswarm=true"
diff --git a/docs/content/reference/dynamic-configuration/swarm.md b/docs/content/reference/dynamic-configuration/swarm.md
new file mode 100644
index 0000000000..67fec341c4
--- /dev/null
+++ b/docs/content/reference/dynamic-configuration/swarm.md
@@ -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"
+```
diff --git a/docs/content/reference/dynamic-configuration/swarm.yml b/docs/content/reference/dynamic-configuration/swarm.yml
new file mode 100644
index 0000000000..6f9e1c62f5
--- /dev/null
+++ b/docs/content/reference/dynamic-configuration/swarm.yml
@@ -0,0 +1,3 @@
+- "traefik.enable=true"
+- "traefik.docker.network=foobar"
+- "traefik.docker.lbswarm=true"
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index ac90226ce8..eff863985e 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -532,7 +532,7 @@ Constraints is an expression that Traefik matches against the container's labels
 Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
 
 `--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`:  
 Expose containers by default. (Default: ```true```)
@@ -543,12 +543,6 @@ Client timeout for HTTP connections. (Default: ```0```)
 `--providers.docker.network`:  
 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`:  
 TLS CA
 
@@ -855,6 +849,51 @@ Enable Rest backend with default settings. (Default: ```false```)
 `--providers.rest.insecure`:  
 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`:  
 Enable ZooKeeper backend with default settings. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 31748c91cf..f83ae69abe 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -532,7 +532,7 @@ Constraints is an expression that Traefik matches against the container's labels
 Default rule. (Default: ```Host(`{{ normalize .Name }}`)```)
 
 `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`:  
 Expose containers by default. (Default: ```true```)
@@ -543,12 +543,6 @@ Client timeout for HTTP connections. (Default: ```0```)
 `TRAEFIK_PROVIDERS_DOCKER_NETWORK`:  
 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`:  
 TLS CA
 
@@ -855,6 +849,51 @@ Enable Rest backend with default settings. (Default: ```false```)
 `TRAEFIK_PROVIDERS_REST_INSECURE`:  
 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`:  
 Enable ZooKeeper backend with default settings. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index b08074bb60..01f1db6d87 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -83,9 +83,7 @@
     defaultRule = "foobar"
     exposedByDefault = true
     useBindPortIP = true
-    swarmMode = true
     network = "foobar"
-    swarmModeRefreshSeconds = "42s"
     httpClientTimeout = "42s"
     allowEmptyServices = true
     [providers.docker.tls]
@@ -93,6 +91,22 @@
       cert = "foobar"
       key = "foobar"
       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]
     directory = "foobar"
     watch = true
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index cb6f3c257b..3f7beb79d6 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -95,9 +95,23 @@ providers:
       insecureSkipVerify: true
     exposedByDefault: true
     useBindPortIP: true
-    swarmMode: true
     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
     allowEmptyServices: true
   file:
diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md
index 08e8e43ada..4a2b32436a 100644
--- a/docs/content/routing/providers/docker.md
+++ b/docs/content/routing/providers/docker.md
@@ -83,54 +83,6 @@ Attach labels to your containers and let Traefik do the rest!
           - 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
 
 !!! info "Labels"
@@ -275,9 +227,6 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
     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_1)).
-    {: #port }
-
     ```yaml
     - "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
     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.
diff --git a/docs/content/routing/providers/swarm.md b/docs/content/routing/providers/swarm.md
new file mode 100644
index 0000000000..cce7ddb010
--- /dev/null
+++ b/docs/content/routing/providers/swarm.md
@@ -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.
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 3237cc82e9..a0ca066e2e 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -75,6 +75,7 @@ nav:
   - 'Configuration Discovery':
       - 'Overview': 'providers/overview.md'
       - 'Docker': 'providers/docker.md'
+      - 'Swarm': 'providers/swarm.md'
       - 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md'
       - 'Kubernetes Ingress': 'providers/kubernetes-ingress.md'
       - 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md'
@@ -94,6 +95,7 @@ nav:
       - 'Services': 'routing/services/index.md'
       - 'Providers':
           - 'Docker': 'routing/providers/docker.md'
+          - 'Swarm': 'routing/providers/swarm.md'
           - 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md'
           - 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md'
           - 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md'
@@ -196,6 +198,7 @@ nav:
       - 'Dynamic Configuration':
         - 'File': 'reference/dynamic-configuration/file.md'
         - 'Docker': 'reference/dynamic-configuration/docker.md'
+        - 'Swarm': 'reference/dynamic-configuration/swarm.md'
         - 'Kubernetes CRD': 'reference/dynamic-configuration/kubernetes-crd.md'
         - 'Kubernetes Gateway API': 'reference/dynamic-configuration/kubernetes-gateway.md'
         - 'Consul Catalog': 'reference/dynamic-configuration/consul-catalog.md'
diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml
index 2cbbf5a60f..86242772cf 100644
--- a/pkg/config/dynamic/fixtures/sample.toml
+++ b/pkg/config/dynamic/fixtures/sample.toml
@@ -40,9 +40,7 @@
     defaultRule = "foobar"
     exposedByDefault = true
     useBindPortIP = true
-    swarmMode = true
     network = "foobar"
-    swarmModeRefreshSeconds = 42
     httpClientTimeout = 42
     [providers.docker.tls]
       ca = "foobar"
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index 2b2afb386f..d71dec626d 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -210,7 +210,9 @@ func (t *Tracing) SetDefaults() {
 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"`
 
-	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"`
 	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"`
@@ -219,12 +221,11 @@ 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"`
 	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"`
-
-	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"`
-	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"`
-	Redis     *redis.Provider         `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	HTTP      *http.Provider          `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,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"`
+	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"`
+	Redis             *redis.Provider                `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	HTTP              *http.Provider                 `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 
 	Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
 }
@@ -265,15 +266,21 @@ func (c *Configuration) SetEffectiveConfiguration() {
 	}
 
 	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 {
 			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.
 	if c.Experimental == nil || !c.Experimental.KubernetesGateway {
 		c.Providers.KubernetesGateway = nil
diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go
index 009aa9c938..9aebb5c340 100644
--- a/pkg/provider/aggregator/aggregator.go
+++ b/pkg/provider/aggregator/aggregator.go
@@ -80,6 +80,10 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
 		p.quietAddProvider(conf.Docker)
 	}
 
+	if conf.Swarm != nil {
+		p.quietAddProvider(conf.Swarm)
+	}
+
 	if conf.Rest != nil {
 		p.quietAddProvider(conf.Rest)
 	}
diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go
index 6095b32f5f..d69fbbe816 100644
--- a/pkg/provider/docker/config.go
+++ b/pkg/provider/docker/config.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	dockertypes "github.com/docker/docker/api/types"
+	"github.com/docker/docker/client"
 	"github.com/docker/go-connections/nat"
 	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/pkg/config/dynamic"
@@ -17,7 +18,16 @@ import (
 	"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)
 
 	for _, container := range containersInspected {
@@ -92,7 +102,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
 	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)
 
 	if len(configuration.Services) == 0 {
@@ -117,7 +127,7 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
 	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)
 
 	if len(configuration.Services) == 0 {
@@ -141,7 +151,7 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
 	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)
 
 	if len(configuration.Services) == 0 {
@@ -167,7 +177,7 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
 	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)
 
 	if !container.ExtraConf.Enable {
@@ -193,7 +203,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
 	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 {
 		return errors.New("load-balancer is not defined")
 	}
@@ -219,7 +229,7 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB
 	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 {
 		return errors.New("load-balancer is not defined")
 	}
@@ -245,7 +255,7 @@ func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadB
 	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 {
 		return errors.New("load-balancer is not defined")
 	}
@@ -275,7 +285,7 @@ func (p *Provider) addServer(ctx context.Context, container dockerData, loadBala
 	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)
 
 	var ip, port string
@@ -307,7 +317,7 @@ func (p *Provider) getIPPort(ctx context.Context, container dockerData, serverPo
 	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)
 
 	netNotFound := false
@@ -338,23 +348,17 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
 	}
 
 	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()
-		containerInspected, err := dockerClient.ContainerInspect(context.Background(), connectedContainer)
+		containerInspected, err := p.apiClient.ContainerInspect(context.Background(), connectedContainer)
 		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)
 			return ""
 		}
 
-		// Check connected container for traefik.docker.network, falling back to
-		// the network specified on the current container.
+		// Check connected container for traefik.docker.network,
+		// falling back to the network specified on the current container.
 		containerParsed := parseContainer(containerInspected)
-		extraConf, err := p.getConfiguration(containerParsed)
+		extraConf, err := p.extractLabels(containerParsed)
 		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)
 			return ""
@@ -379,8 +383,9 @@ func (p Provider) getIPAddress(ctx context.Context, container dockerData) string
 	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)
+
 	for netPort, portBindings := range container.NetworkSettings.Ports {
 		if strings.EqualFold(string(netPort), port+"/TCP") || strings.EqualFold(string(netPort), port+"/UDP") {
 			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)
 }
-
-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)
-}
diff --git a/pkg/provider/docker/data.go b/pkg/provider/docker/data.go
new file mode 100644
index 0000000000..a4af51d825
--- /dev/null
+++ b/pkg/provider/docker/data.go
@@ -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
+}
diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go
deleted file mode 100644
index ec48f714ce..0000000000
--- a/pkg/provider/docker/docker.go
+++ /dev/null
@@ -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
-}
diff --git a/pkg/provider/docker/pdocker.go b/pkg/provider/docker/pdocker.go
new file mode 100644
index 0000000000..1259782c4b
--- /dev/null
+++ b/pkg/provider/docker/pdocker.go
@@ -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
+}
diff --git a/pkg/provider/docker/pswarm.go b/pkg/provider/docker/pswarm.go
new file mode 100644
index 0000000000..fd513dad22
--- /dev/null
+++ b/pkg/provider/docker/pswarm.go
@@ -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
+}
diff --git a/pkg/provider/docker/shared.go b/pkg/provider/docker/shared.go
new file mode 100644
index 0000000000..74bcc62a18
--- /dev/null
+++ b/pkg/provider/docker/shared.go
@@ -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)
+}
diff --git a/pkg/provider/docker/label.go b/pkg/provider/docker/shared_labels.go
similarity index 94%
rename from pkg/provider/docker/label.go
rename to pkg/provider/docker/shared_labels.go
index b968eedf1a..f17e515080 100644
--- a/pkg/provider/docker/label.go
+++ b/pkg/provider/docker/shared_labels.go
@@ -23,7 +23,7 @@ type specificConfiguration struct {
 	LBSwarm bool
 }
 
-func (p *Provider) getConfiguration(container dockerData) (configuration, error) {
+func (p *Shared) extractLabels(container dockerData) (configuration, error) {
 	conf := configuration{
 		Enable: p.ExposedByDefault,
 		Docker: specificConfiguration{
diff --git a/webui/src/statics/providers/swarm.svg b/webui/src/statics/providers/swarm.svg
new file mode 100644
index 0000000000..db4a729e68
--- /dev/null
+++ b/webui/src/statics/providers/swarm.svg
@@ -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>


Test patch:
diff --git a/pkg/api/handler_overview_test.go b/pkg/api/handler_overview_test.go
index d76dd55e57..6095e11eb3 100644
--- a/pkg/api/handler_overview_test.go
+++ b/pkg/api/handler_overview_test.go
@@ -235,6 +235,7 @@ func TestHandler_Overview(t *testing.T) {
 				API:    &static.API{},
 				Providers: &static.Providers{
 					Docker:            &docker.Provider{},
+					Swarm:             &docker.SwarmProvider{},
 					File:              &file.Provider{},
 					KubernetesIngress: &ingress.Provider{},
 					KubernetesCRD:     &crd.Provider{},
diff --git a/pkg/api/testdata/overview-providers.json b/pkg/api/testdata/overview-providers.json
index 20f1343542..16e757981b 100644
--- a/pkg/api/testdata/overview-providers.json
+++ b/pkg/api/testdata/overview-providers.json
@@ -24,6 +24,7 @@
 	},
 	"providers": [
 		"Docker",
+		"Swarm",
 		"File",
 		"KubernetesIngress",
 		"KubernetesCRD",
diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go
index 5f0048b2ac..829c5905d0 100644
--- a/pkg/provider/docker/config_test.go
+++ b/pkg/provider/docker/config_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/traefik/traefik/v3/pkg/config/dynamic"
 )
 
-func TestDefaultRule(t *testing.T) {
+func TestDynConfBuilder_DefaultRule(t *testing.T) {
 	testCases := []struct {
 		desc        string
 		containers  []dockerData
@@ -376,8 +376,10 @@ func TestDefaultRule(t *testing.T) {
 			t.Parallel()
 
 			p := Provider{
-				ExposedByDefault: true,
-				DefaultRule:      test.defaultRule,
+				Shared: Shared{
+					ExposedByDefault: true,
+					DefaultRule:      test.defaultRule,
+				},
 			}
 
 			err := p.Init()
@@ -385,18 +387,20 @@ func TestDefaultRule(t *testing.T) {
 
 			for i := 0; i < len(test.containers); i++ {
 				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)
 			}
 
-			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)
 		})
 	}
 }
 
-func Test_buildConfiguration(t *testing.T) {
+func TestDynConfBuilder_build(t *testing.T) {
 	testCases := []struct {
 		desc               string
 		containers         []dockerData
@@ -3381,10 +3385,12 @@ func Test_buildConfiguration(t *testing.T) {
 			t.Parallel()
 
 			p := Provider{
-				AllowEmptyServices: test.allowEmptyServices,
-				DefaultRule:        "Host(`{{ normalize .Name }}.traefik.wtf`)",
-				ExposedByDefault:   true,
-				UseBindPortIP:      test.useBindPortIP,
+				Shared: Shared{
+					AllowEmptyServices: test.allowEmptyServices,
+					ExposedByDefault:   true,
+					UseBindPortIP:      test.useBindPortIP,
+					DefaultRule:        "Host(`{{ normalize .Name }}.traefik.wtf`)",
+				},
 			}
 			p.Constraints = test.constraints
 
@@ -3393,18 +3399,20 @@ func Test_buildConfiguration(t *testing.T) {
 
 			for i := 0; i < len(test.containers); i++ {
 				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)
 			}
 
-			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)
 		})
 	}
 }
 
-func TestDockerGetIPPort(t *testing.T) {
+func TestDynConfBuilder_getIPPort_docker(t *testing.T) {
 	type expected struct {
 		ip    string
 		port  string
@@ -3565,12 +3573,12 @@ func TestDockerGetIPPort(t *testing.T) {
 
 			dData := parseContainer(test.container)
 
-			provider := &Provider{
+			builder := NewDynConfBuilder(Shared{
 				Network:       "testnet",
 				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 {
 				require.Error(t, actualError)
 			} else {
@@ -3582,73 +3590,7 @@ func TestDockerGetIPPort(t *testing.T) {
 	}
 }
 
-func TestDockerGetPort(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) {
+func TestDynConfBuilder_getIPAddress_docker(t *testing.T) {
 	testCases := []struct {
 		desc      string
 		container docker.ContainerJSON
@@ -3742,24 +3684,26 @@ func TestDockerGetIPAddress(t *testing.T) {
 		t.Run(test.desc, func(t *testing.T) {
 			t.Parallel()
 
-			provider := &Provider{
+			conf := Shared{
 				Network: "webnet",
 			}
 
 			dData := parseContainer(test.container)
 
-			dData.ExtraConf.Docker.Network = provider.Network
+			dData.ExtraConf.Docker.Network = conf.Network
 			if len(test.network) > 0 {
 				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)
 		})
 	}
 }
 
-func TestSwarmGetIPAddress(t *testing.T) {
+func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
 	testCases := []struct {
 		service  swarm.Service
 		expected string
@@ -3810,47 +3754,13 @@ func TestSwarmGetIPAddress(t *testing.T) {
 		t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
 			t.Parallel()
 
-			provider := &Provider{
-				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{}
+			p := &SwarmProvider{}
 
 			dData, err := p.parseService(context.Background(), test.service, test.networks)
 			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)
 		})
 	}
diff --git a/pkg/provider/docker/pswarm_mock_test.go b/pkg/provider/docker/pswarm_mock_test.go
new file mode 100644
index 0000000000..83eb61b3bf
--- /dev/null
+++ b/pkg/provider/docker/pswarm_mock_test.go
@@ -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
+}
diff --git a/pkg/provider/docker/swarm_test.go b/pkg/provider/docker/pswarm_test.go
similarity index 86%
rename from pkg/provider/docker/swarm_test.go
rename to pkg/provider/docker/pswarm_test.go
index 541af22f22..d13e3e7c30 100644
--- a/pkg/provider/docker/swarm_test.go
+++ b/pkg/provider/docker/pswarm_test.go
@@ -9,26 +9,10 @@ import (
 	"github.com/davecgh/go-spew/spew"
 	dockertypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
-	dockerclient "github.com/docker/docker/client"
 	"github.com/stretchr/testify/assert"
 	"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) {
 	testCases := []struct {
 		service       swarm.Service
@@ -83,7 +67,7 @@ func TestListTasks(t *testing.T) {
 		t.Run(strconv.Itoa(caseID), func(t *testing.T) {
 			t.Parallel()
 
-			p := Provider{}
+			p := SwarmProvider{}
 			dockerData, err := p.parseService(context.Background(), test.service, test.networks)
 			require.NoError(t, err)
 
@@ -103,32 +87,7 @@ func TestListTasks(t *testing.T) {
 	}
 }
 
-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
-}
-
-func TestListServices(t *testing.T) {
+func TestSwarmProvider_listServices(t *testing.T) {
 	testCases := []struct {
 		desc             string
 		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}
 
-			p := Provider{}
+			p := SwarmProvider{}
 
 			serviceDockerData, err := p.listServices(context.Background(), dockerClient)
 			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 {
 		service     swarm.Service
 		tasks       []swarm.Task
@@ -396,7 +355,7 @@ func TestSwarmTaskParsing(t *testing.T) {
 		t.Run(strconv.Itoa(caseID), func(t *testing.T) {
 			t.Parallel()
 
-			p := Provider{}
+			p := SwarmProvider{}
 
 			dData, err := p.parseService(context.Background(), test.service, test.networks)
 			require.NoError(t, err)
diff --git a/pkg/provider/docker/shared_test.go b/pkg/provider/docker/shared_test.go
new file mode 100644
index 0000000000..324363e846
--- /dev/null
+++ b/pkg/provider/docker/shared_test.go
@@ -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)
+		})
+	}
+}
diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go
index c8836d2d0a..23ce894d8e 100644
--- a/pkg/redactor/redactor_config_test.go
+++ b/pkg/redactor/redactor_config_test.go
@@ -39,7 +39,7 @@ import (
 	"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
 
@@ -591,22 +591,46 @@ func TestDo_staticConfiguration(t *testing.T) {
 	}
 
 	config.Providers.Docker = &docker.Provider{
-		Constraints: `Label("foo", "bar")`,
-		Watch:       true,
-		Endpoint:    "MyEndPoint",
-		DefaultRule: "PathPrefix(`/`)",
-		TLS: &types.ClientTLS{
-			CA:                 "myCa",
-			Cert:               "mycert.pem",
-			Key:                "mycert.key",
-			InsecureSkipVerify: true,
+		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,
+		},
+	}
+
+	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,
 		},
-		ExposedByDefault:        true,
-		UseBindPortIP:           true,
-		SwarmMode:               true,
-		Network:                 "MyNetwork",
-		SwarmModeRefreshSeconds: 42,
-		HTTPClientTimeout:       42,
+		RefreshSeconds: 42,
 	}
 
 	config.Providers.KubernetesIngress = &ingress.Provider{
diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json
index 0097aa2c29..a8db724607 100644
--- a/pkg/redactor/testdata/anonymized-static-config.json
+++ b/pkg/redactor/testdata/anonymized-static-config.json
@@ -89,22 +89,39 @@
   "providers": {
     "providersThrottleDuration": "1m51s",
     "docker": {
+      "exposedByDefault": true,
       "constraints": "Label(\"foo\", \"bar\")",
+      "allowEmptyServices": true,
+      "network": "MyNetwork",
+      "useBindPortIP": true,
       "watch": true,
-      "endpoint": "xxxx",
       "defaultRule": "xxxx",
+      "endpoint": "xxxx",
       "tls": {
         "ca": "xxxx",
         "cert": "xxxx",
         "key": "xxxx",
         "insecureSkipVerify": true
       },
+      "httpClientTimeout": "42ns"
+    },
+    "swarm": {
       "exposedByDefault": true,
-      "useBindPortIP": true,
-      "swarmMode": true,
+      "constraints": "Label(\"foo\", \"bar\")",
+      "allowEmptyServices": true,
       "network": "MyNetwork",
-      "swarmModeRefreshSeconds": "42ns",
-      "httpClientTimeout": "42ns"
+      "useBindPortIP": true,
+      "watch": true,
+      "defaultRule": "xxxx",
+      "endpoint": "xxxx",
+      "tls": {
+        "ca": "xxxx",
+        "cert": "xxxx",
+        "key": "xxxx",
+        "insecureSkipVerify": true
+      },
+      "httpClientTimeout": "42ns",
+      "refreshSeconds": "42ns"
     },
     "file": {
       "directory": "file Directory",