There is a moment in every self-hosting story where one Docker host stops being enough. You want a container to come back on another machine when a node dies, you want traffic spread across replicas, and you would rather not spend a quarter learning a new control plane to get there. Swarm mode is the option most people skip at that point, usually because someone told them it was dead.

It is not dead, but the honest answer is more interesting than that, and it changes who should actually pick it. So this post does two things: it walks through a real Swarm setup from zero, and it tells you plainly what state the project is in as of mid 2026.

Is Docker Swarm dead in 2026?

First, untangle two different products that share a name.

Docker Classic Swarm was a separate standalone clustering tool. It is gone. Docker's own deprecation page lists Classic Swarm and overlay networks using external key/value stores as deprecated in Engine v20.10 and removed in v23.0, along with the --cluster-store and --cluster-advertise daemon flags. The Swarm mode overview page states it directly: "Do not confuse Docker Swarm mode with Docker Classic Swarm which is no longer actively developed."

Swarm mode is the other thing. It is built into the Docker Engine, it is what docker swarm init starts, and it is what this post is about. Here is what I could actually verify about its health, rather than what the internet feels about it.

The current docs carry no deprecation notice, no end of life date, and no maintenance mode banner on any page under docs.docker.com/engine/swarm/. Swarm mode does not appear on the deprecated features page at all. Worth being precise here: the absence of a notice is what is verifiable, not a promise of support.

Engine 29 introduced an nftables firewall backend, and Docker's own release blog says it cannot be enabled on a node with Swarm enabled, because "Swarm support is planned for a future release." Swarm is behind on a new engine capability, and Docker publicly intends to catch it up.

Who actually maintains Swarm commercially

Mirantis bought the Docker Enterprise business in November 2019 and owns commercial Swarm support today. In July 2025 it published a commitment that "Swarm will be fully supported through at least 2030 as part of Mirantis Kubernetes Engine 3."

Read that with three caveats. A follow up post a month later softened the number to "at least for the next three years." The furthest actually documented end of life date for a Swarm capable release is MKE 3.9, which lists 24 March 2028. And MKE 4, the k0s based next generation, contains no Swarm at all; Then in May 2026 Mirantis agreed to be acquired by IREN, and I found no statement after that announcement re-confirming the 2030 roadmap.

So the fair summary is this. Swarm mode is alive, actively maintained at the bugfix and security level, feature-stable rather than feature-frozen, upstream in the engine you already run, and commercially backed by a company going through an ownership change. That is a very different risk profile from "dead", and also a very different one from Kubernetes.

What Swarm mode gives you that plain Docker does not

Swarm mode turns a set of independent Docker engines into one scheduling target. You declare that a service should have three replicas and the cluster keeps three replicas running, restarting and rescheduling tasks on healthy nodes when something dies. Service names resolve through internal DNS, requests are load balanced across tasks, and node to node control traffic runs over mutual TLS by default.

It also brings two features that standalone Docker simply does not have: secrets and configs, distributed to only the services granted access, plus overlay networking that spans hosts so containers on different machines share a subnet. Rolling updates and rollback are part of the service model rather than something you script yourself.

The trade is scope. There are no operators, no autoscaling on custom metrics, no admission controllers, and a much smaller ecosystem. You get roughly the eighty percent of orchestration that a small fleet needs, with about five percent of the operational surface.

How to set up a Docker Swarm cluster from zero

Assume three Linux VMs with the Docker Engine already installed, on a private network. Before touching Swarm, open the right ports between them: TCP 2377 for the manager control plane, TCP and UDP 7946 for node discovery, and UDP 4789 for the overlay data path. That last one matters more than people think, because it carries VXLAN traffic and the docs are blunt that VXLAN provides no authentication, so untrusted traffic must never reach 4789.

On the first node, initialize the swarm and tell it which address the others should reach it on.

# Run on the first node only. Use its private IP, not a public one.
docker swarm init --advertise-addr 10.20.30.11

That command prints a docker swarm join line containing a worker token. Run it on the other two machines and they join as workers. If you lose it, print it again from a manager with docker swarm join-token worker, or ask for the manager token with docker swarm join-token manager.

# Run on each of the other nodes, using the token printed by swarm init.
docker swarm join --token SWMTKN-1-xxxxxxxx 10.20.30.11:2377

Now list the nodes from the first machine. Only managers can run cluster commands, so docker node ls on a worker fails with "This node is not a swarm manager", which is expected rather than broken.

docker node ls
docker node promote vm-test-2 vm-test-3
docker node ls

After the promotion, all three nodes are managers, one is the leader, and the other two show a reachable manager status. Managers also run workloads by default, which is exactly what you want on a three node fleet and exactly what you do not want on a large one, where you drain them with docker node update --availability drain.

How many managers do you need for high availability?

This is where a lot of homelab Swarm advice is slightly wrong, so use the actual Raft numbers. The docs state that Raft "tolerates up to (N-1)/2 failures and requires a majority or quorum of (N/2)+1 members to agree." That gives you the table that matters: three managers survive one failure, five survive two, seven survive three.

An odd number is a recommendation, not a requirement. Docker spells it out: whether you have three managers or four, you can still only lose one and keep quorum, so the fourth manager buys you nothing except another machine to patch. Two managers is legal and gives you zero fault tolerance, which is strictly worse than one manager plus workers because you now have two machines whose failure stops scheduling.

What happens when quorum is lost is the part worth internalizing before you need it. Existing tasks on existing workers keep running and keep serving traffic. What stops is the control plane: no nodes added, updated or removed, no tasks started, stopped, moved or updated. Your site stays up and you lose the ability to change anything, which is a much gentler failure than it sounds, and a very unpleasant one if it coincides with a deploy. If you have more than one failure domain, the docs recommend spreading managers over at least three availability zones, two-two-one for five managers.

Creating your first Swarm service

A container is something you run on one engine. A service is a declaration the cluster keeps true.

docker service create \
  --name edge \
  --replicas 2 \
  --publish 8081:80 \
  nginx:1.27

Then inspect it. docker service ls shows the desired and running replica counts across the whole cluster, and docker service ps edge shows which node each task landed on, including the history of replaced tasks.

docker service ls
docker service ps edge

Two habits to unlearn here. docker ps on a node shows only that node's containers, because it is a standalone command that knows nothing about the cluster. And killing a container by hand is not a way to stop anything; the scheduler notices the missing task and starts a replacement, which is the entire point.

Placement is controlled with node labels and constraints rather than by choosing hosts yourself.

docker node update --label-add tier=edge vm-test-2
docker node update --label-add tier=edge vm-test-3

docker service create \
  --name edge \
  --replicas 3 \
  --constraint node.labels.tier==edge \
  --publish 8081:80 \
  nginx:1.27

Constraints are hard. If no node satisfies them, tasks sit in Pending forever instead of being placed somewhere convenient, and the operator == or != is all you get. Placement preferences are the soft version: best effort, and deployment still succeeds when nothing matches.

How does traffic reach a node that is not running the container?

Send a request to any node in the swarm on a published port and you get an answer, even from a node where no task for that service is running. That is the ingress routing mesh, and the docs describe it as every node accepting connections on published ports for any service in the swarm, then routing to an active container. On the node you hit, port 8081 may not even be bound.

# All three answer, even though only two nodes run a task.
curl -s -o /dev/null -w '%{http_code}\n' http://10.20.30.11:8081/
curl -s -o /dev/null -w '%{http_code}\n' http://10.20.30.12:8081/
curl -s -o /dev/null -w '%{http_code}\n' http://10.20.30.13:8081/

This is the feature that makes small Swarm clusters pleasant. You point a plain DNS round robin or a single external load balancer at all node IPs and you are done, with no ingress controller to install and no MetalLB equivalent to reason about.

Two caveats the docs are explicit about. Ingress traffic is unencrypted by default, and the routing mesh does not use direct server return, so the node you hit stays in the response path. When you need the client IP or want to skip the mesh entirely, publish in host mode with --publish mode=host,target=80,published=8081, which binds the port on the node running the task and gives up the mesh in exchange.

Rolling updates and rollback without a deploy script

Updating a service image is one command, and the interesting part is the flags around it.

docker service update \
  --image nginx:1.27.3 \
  --update-parallelism 1 \
  --update-delay 15s \
  --update-monitor 30s \
  --update-order start-first \
  --update-failure-action rollback \
  edge

The behavioural defaults, as described in Docker's service documentation, are one task at a time with a 30 second monitor window and pause as the failure action. Note that the CLI reference prints 0 and 0s for --update-parallelism and --update-monitor while the prose documents one task and 30 seconds; treat the prose as the behaviour and set the values explicitly if it matters to you. If any task returns FAILED during the update, the scheduler pauses, which means a bad image leaves you half deployed and waiting unless you set --update-failure-action rollback.

--update-order start-first is worth making a default for anything serving traffic. It starts the new task before stopping the old one, at the cost of briefly running replicas + 1 tasks. Going back is also one command, and the rollback path has its own separate defaults: parallelism one, a five second monitor, pause on failure.

docker service update --rollback edge

Deploying a Compose file with docker stack deploy

Here is the biggest practical trap in Swarm, and it has nothing to do with clustering.

Swarm stacks use docker stack deploy and take a Compose file, which sounds like your existing files just work. They mostly do not, and Docker's own note is unambiguous: "The docker stack deploy command uses the legacy Compose file version 3 format, used by Compose V1. The latest format, defined by the Compose specification isn't compatible with the docker stack deploy command." The legacy v2 and v3 references have moved to the Compose V1 branch and are no longer actively maintained.

So a stack file is a legacy versioned document, which is why stack examples still carry a top level version key that docker compose now treats as obsolete. Three more things get dropped or moved on the way in. container_name is not supported, because names are assigned per task. restart: is ignored, because restart behaviour belongs to the scheduler under deploy.restart_policy. And everything about replicas, placement and update strategy lives under deploy, which plain docker compose ignores completely.

version: "3.8"

services:
  edge:
    image: nginx:1.27
    ports:
      - "8081:80"
    networks:
      - proxy
    configs:
      - source: edge_site_conf
        target: /etc/nginx/conf.d/site.conf
    secrets:
      - source: upstream_api_token
        target: upstream_api_token
    deploy:
      replicas: 3
      placement:
        constraints:
          - node.labels.tier == edge
      update_config:
        order: start-first
        parallelism: 1
        delay: 15s
        failure_action: rollback
      restart_policy:
        condition: on-failure

networks:
  proxy:
    external: true

configs:
  edge_site_conf:
    external: true

secrets:
  upstream_api_token:
    external: true

Deploy it from a manager node. docker stack deploy is a cluster management command and fails anywhere else.

docker stack deploy -c stack.yml edge-stack
docker stack services edge-stack
docker stack ps edge-stack

Add --prune when you want services removed from the file to also disappear from the cluster, which is the closest Swarm gets to a declarative apply.

The reassuring part of all this: enabling Swarm mode on a host does not touch anything already running there. Existing standalone containers and existing docker compose projects keep running exactly as they were, and they do not become services. You can turn Swarm on, migrate one project a week, and run both models side by side for as long as you like.

Secrets and configs, and the trap that catches everyone

Swarm secrets are encrypted in transit and at rest in the Raft log, are readable only by services explicitly granted them, and cap out at 500 KB.

printf '%s' 'super-secret-token' | docker secret create upstream_api_token -
docker config create edge_site_conf ./site.conf
docker secret ls
docker config ls

Inside a task, a secret is a file at /run/secrets/<target> on a memory backed mount, and a config is a plain file at whatever path you chose. Now the trap. The secret is a file, not an environment variable, so an image that only reads credentials from environment variables cannot use it directly. Pointing an env var at the path only works when the application supports the *_FILE convention, as Postgres and Traefik do.

    environment:
      # This works only because the image reads the *_FILE variant itself.
      UPSTREAM_API_TOKEN_FILE: /run/secrets/upstream_api_token

When an image has no _FILE support, the usual workaround is an entrypoint wrapper that reads the file and exports the variable before exec'ing the real process. It costs you a couple of lines and keeps the secret out of the image and out of the stack file.

#!/bin/sh
# entrypoint.sh, for images that only read env vars
set -eu
if [ -f /run/secrets/upstream_api_token ]; then
  UPSTREAM_API_TOKEN="$(cat /run/secrets/upstream_api_token)"
  export UPSTREAM_API_TOKEN
fi
exec "$@"

Two more details that will save you time. Configs are not encrypted at rest and are mounted directly into the filesystem without a RAM disk, so despite living in the same cluster store they are not a secrets substitute. And neither secrets nor configs can be edited: there is no update command, so changing one means creating a new object under a new name and updating the service to point at it. That is annoying by hand and completely fine once your deploy appends a content hash or a timestamp to the name.

Overlay networks across hosts

Overlay networks are the reason services on different machines can talk as if they shared a switch. Swarm creates an ingress overlay automatically for the routing mesh, and each stack gets its own default overlay. What you usually want on top of that is one long lived shared network for things like a reverse proxy.

docker network create \
  --driver overlay \
  --attachable \
  proxy

--attachable is the flag people miss. Without it, only Swarm services can join, and the ingress network is not attachable at all. With it, standalone containers and plain docker compose projects on any node can attach to the same overlay, which is what makes a gradual migration bearable: your not yet migrated Compose stack and your new Swarm service sit on one network and find each other by name.

You can add --opt encrypted for IPsec encryption of the data path. It is real encryption with a real cost, so it belongs on control and credential traffic rather than on the network path of a chatty database.

Volumes are the part that will bite you

Everything above is genuinely simple - storage is not, and it is where small Swarm clusters actually hurt.

A named volume in Swarm is local to the node the task landed on. Reschedule that task and it starts with an empty volume on the new node while the old data sits on the old machine. Swarm will happily do this to your database at three in the morning.

There are three answers. Pin stateful services to one node with a constraint and accept that this piece has no failover, which is what most homelabs do and which is a perfectly defensible choice as long as it is a choice. Put the data outside the cluster on a NAS and mount it over a network filesystem, which works and is what many people run, with the caveat that NFS under a database is a well known source of locking and performance surprises. Or use a proper storage driver: Mirantis added CSI support to its Swarm capable platform specifically so Swarm could run stateful workloads.

What you should not do is deploy Postgres as a three replica Swarm service with a named volume and assume the cluster is handling durability for you.

Docker Swarm vs Kubernetes vs Kamal - Which should you pick?

Three different tools that people compare because they all end with containers running on servers.

Pick Swarm mode when you own a handful of Linux hosts, you already think in Compose, and you want rescheduling on node failure plus internal load balancing without operating a control plane. It is the right answer for a homelab, a small internal fleet, or a product running on three to ten VMs where nobody is paid to be the platform person. You should accept two things going in: the legacy Compose format, and a feature set that will look much the same in three years.

Pick Kubernetes, or k3s if you want the same API with less machinery, when you need the ecosystem rather than the scheduler. Operators, autoscaling, CSI and CNI choice, policy, a managed control plane from a cloud, and a hiring pool that already knows it. That ecosystem is the entire value proposition and it is a real one; the cost is a permanent operational surface that Swarm does not have.

Pick Kamal when you deploy one application to a few servers and you do not actually need a cluster. This is the comparison people get wrong most often, and Kamal's own site is refreshingly clear about it: "Docker Swarm is much simpler than Kubernetes, but it's still built on the same declarative model that uses state reconciliation. Kamal is intentionally designed around imperative commands, like Capistrano." That is a design choice and it has consequences. Kamal gives you zero downtime deploys through kamal-proxy, health checks on /up, roles for web and worker hosts, configurable rolling boot with limit and wait, and image based rollback. It does not give you a scheduler, cluster membership, quorum, or anything that restarts your container on a different host when a machine dies. Its proxy runs per server rather than balancing across servers, and its own docs are explicit that accessories "are not updated when you deploy, and they do not have zero-downtime deployments."

So the dividing line is not simplicity, since both are simple. It is whether you want desired state reconciliation at all. If your answer to a dead node is "the load balancer takes it out and I redeploy", Kamal plus a slightly bigger server will serve one Rails app better than Swarm will. If your answer is "the cluster should put those containers somewhere else without me", that is Swarm's whole job, and Kubernetes is the version of that job with a career attached.

A migration checklist you can actually follow

  1. Open 2377/tcp, 7946/tcp+udp, 4789/udp between nodes only, never to the internet.
  2. docker swarm init --advertise-addr <private-ip> on node one, join the rest, then promote to three managers.
  3. Create one --attachable overlay network for shared traffic before migrating anything.
  4. Move your least important stateless service first, converting its Compose file to a v3 stack file.
  5. Delete container_name and restart:, then add deploy with replicas, constraints and update_config.
  6. Move credentials into docker secret objects and verify the image can read them from /run/secrets.
  7. Decide explicitly for every stateful service: pinned to one node, external storage, or a real storage driver.
  8. Test a rolling update with --update-order start-first and --update-failure-action rollback before you need it.
  9. Kill a node on purpose and watch where the tasks land. If that surprises you, fix it now rather than at 3am.

FAQ

Is Docker Swarm deprecated?

Swarm mode is not listed anywhere on Docker's deprecated features page and its documentation carries no deprecation notice. Classic Swarm, the separate older product, was deprecated in Engine v20.10 and removed in v23.0. The two are constantly confused, and that confusion is where most "Swarm is dead" claims come from.

Does Docker Swarm support modern Compose files?

Only partially, and this is the thing to check before committing. docker stack deploy reads the legacy Compose v3 format, and Docker states plainly that the current Compose specification is not compatible with it. Expect to maintain a separate stack file rather than reusing your development Compose file unchanged.

How many manager nodes does a Swarm cluster need?

Three for single node fault tolerance, five to survive two failures, seven to survive three. The quorum requirement is (N/2)+1, so an even count adds a machine without adding tolerance. One manager is fine for a lab, two is worse than one.

What happens to running containers if the Swarm managers lose quorum?

Tasks already running on workers keep running and keep serving traffic. The control plane stops: no scheduling, no updates, no node changes, until quorum returns.

Can I run Docker Compose and Docker Swarm on the same host?

Yes, and it is the sane migration path. Turning on Swarm mode does not convert existing containers or Compose projects into services, so both models run side by side and you migrate at whatever pace you like.

Is Docker Swarm production ready in 2026?

For a small fleet, yes, with your eyes open. It is upstream in the engine, it receives bugfixes and security work in every recent Engine release, and Mirantis sells long term support for it. What it is not is a growing platform: expect stability, not new capabilities, and plan your storage story yourself.

Happy swarming!