Docker Swarm Cluster Architecture
Docker's own orchestrator, already in the engine you have installed. Managers, workers, the raft state store and the two join tokens - read off a real three-node cluster rather than a diagram.
Swarm and Multi-Host (optional) Guide 41 of 46 Intermediate
- Cluster1 manager, 2 workers
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2 on every node
- Architectureamd64
- TimeAbout 13 min
- Reviewed22 August 2026
Tested on the versions above. Captured on a real three-node cluster. Node IDs, IP addresses and join tokens are specific to that cluster - tokens shown here are truncated deliberately and were rotated afterwards. Match the shape of the output, not the values.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Swarm Manager (Leader) | 2 Core | 4 GB | 50 GB |
| DOCKER02 | 192.168.0.22 | Ubuntu 26.04 LTS | Swarm Worker | 2 Core | 4 GB | 50 GB |
| DOCKER03 | 192.168.0.23 | Ubuntu 26.04 LTS | Swarm Worker | 2 Core | 4 GB | 50 GB |
Before you start
- You are comfortable with Compose - guide 24 in this path.
- Overlay and the other network drivers - guide 11 in this path.
- Two or three Linux hosts that can reach each other, if you want to follow along.
-
Why swarm exists, and where it fits
Compose runs several containers on ONE machine. Swarm runs services across SEVERAL, and it is built into the engine - no extra install, no control plane to operate. It is far smaller than Kubernetes in both capability and effort, which is exactly the trade: for a handful of machines running a handful of services, it is often the right amount of orchestrator.
bash Example session docker info --format "swarm={{.Swarm.LocalNodeState}} manager={{.Swarm.ControlAvailable}} nodes={{.Swarm.Nodes}} managers={{.Swarm.Managers}}"managers=1 nodes=3Expected result
swarm=activeon a node that has joined a cluster.Success conditionYou can tell a swarm node from a standalone one with a single command.
inactivemeans this engine is not in a cluster. -
Start a cluster, or join one
One command creates a cluster and makes that machine its first manager. Everything else joins with a token.
--advertise-addrmatters the moment a host has more than one address - swarm has to tell other nodes where to find it, and it will refuse rather than guess.bash docker swarm init --advertise-addr 192.168.0.21Swarm initialized: current node (k64c8bnn79muxpj6m0qf872nb) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-3g4ak6liblug30tvups4qiof941gqo37zacd3sbsaoi5ciit1f-apti3pb5rhn0pdgw8zhuejjmm 192.168.0.21:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.Expected resultA confirmation naming the new manager node.
Success condition
docker node lsworks. It only works on a manager - that is the quickest way to tell which role you are on. -
Read the cluster
docker node lsis the cluster's inventory, and it runs only on a manager. STATUS is whether the node is reachable, AVAILABILITY is whether it may be given work - two different things that people conflate, and the distinction matters in guide 45.bash Example session docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSIONfr31hknhlxd5a9s4324qwqqy1 * ahm-docker01 Ready Active Leader 29.7.2y9vvv2k6w5ur7kjodwnro59ej ahm-docker02 Ready Active 29.7.2vqacq9wzoqon1nkz9bd9m0l3j ahm-docker03 Ready Active 29.7.2Expected resultEvery node listed, with one marked Leader and
*marking the node you are on.Success conditionYou can identify the leader and your own node. Running this on a worker fails - that refusal is diagnostic, not a problem.
-
Managers hold the state; workers run the work
A manager stores the desired state of the whole cluster in a raft log and decides where tasks run. A worker just runs what it is told. Managers also run tasks by default, which is fine for a small cluster and worth turning off for a large one. Inspect shows the split plainly.
bash Example session docker node inspect ahm-docker01 --format "role={{.Spec.Role}} avail={{.Spec.Availability}} addr={{.Status.Addr}} leader={{.ManagerStatus.Leader}} reachability={{.ManagerStatus.Reachability}}"role=manager avail=active addr=192.168.0.21 leader=true reachability=reachabledocker node inspect ahm-docker03 --format "role={{.Spec.Role}} avail={{.Spec.Availability}} addr={{.Status.Addr}} cpus={{.Description.Resources.NanoCPUs}}"role=worker avail=active addr=192.168.0.23 cpus=2000000000Expected resultOne manager with a ManagerStatus, one worker without.
Success conditionA worker has no
.ManagerStatusat all - referencing it on a worker produces nothing, which is how you can test the role in a script. -
Raft, and why manager count is odd
Managers agree on cluster state through raft, which needs a majority to make decisions. Three managers tolerate one failure; five tolerate two. An EVEN number buys nothing - four managers still only tolerate one, because three of four is the majority either way. One manager, as in this cluster, means no fault tolerance at all: lose it and the cluster keeps running but you cannot change anything.
bash docker info --format "managers={{.Swarm.Managers}} nodes={{.Swarm.Nodes}}"managers=1 nodes=3Expected resultThe manager count for your own cluster.
Success conditionYou can state your failure tolerance as a number. Running tasks survive a manager outage; scheduling, scaling and deploys do not.
-
The two join tokens are two different privileges
There is a worker token and a manager token, and they are not interchangeable. Anyone holding the manager token can take administrative control of your cluster, so it belongs with your other high-value credentials. If one leaks, rotate it - the old token dies immediately and existing nodes stay joined.
bash Example session docker swarm join-token worker docker swarm join --token SWMTKN-1-1wodmz4m2o1415q...REDACTED 192.168.0.21:2377# tokens are credentials - these are truncated, and the real ones were rotated after capturedocker swarm join-token --rotate worker -qSWMTKN-1-1wodmz4m2o1415q... # a new token; the previous one no longer worksExpected resultA ready-to-paste join command, and a new token after rotating.
Success conditionYou treat the manager token as an admin credential. Note port 2377 - the cluster management port, which must be reachable between nodes.
-
What has to be open between nodes
Three ports, and a cluster that half-works is almost always one of them being blocked. 2377 is management, 7946 is node-to-node discovery and must be open on BOTH TCP and UDP, and 4789 UDP carries overlay traffic. A firewall allowing only 2377 gives you a cluster that forms and then cannot route anything.
bash Example session sudo ss -ltnp | grep -E "2377|7946"LISTEN 0 4096 *:7946 *:* users:(("dockerd",pid=63915,fd=31))LISTEN 0 4096 *:2377 *:* users:(("dockerd",pid=63915,fd=26))Expected resultdockerd listening on the management and discovery ports.
Success conditionYou know which three ports to check first when nodes join but services cannot talk.
Troubleshooting
This node is not a swarm manager
Why: You ran a cluster command on a worker. Workers can run tasks but hold no cluster state and cannot be queried.
Fix:Run it on a manager. Check where you are before assuming the cluster is broken.
bash docker info --format "{{.Swarm.ControlAvailable}}"# true on a manager, false on a workercould not choose an IP address to advertise
Why: The host has several addresses and swarm will not guess which one other nodes should use.
Fix:Pass
--advertise-addrexplicitly with the address on the network your nodes share.bash ip -o -4 addr show scope globaldocker swarm init --advertise-addr 192.168.0.21A node joins but immediately shows Down
Why: Discovery traffic is blocked. Nodes gossip on 7946 over both TCP and UDP; opening only TCP is the usual mistake.
Fix:Open 2377/tcp, 7946/tcp, 7946/udp and 4789/udp between every pair of nodes.
bash docker node ls --format "{{.Hostname}} {{.Status}}"The manager is gone and nothing can be changed
Why: With a single manager there is no quorum to elect a replacement. Existing tasks keep running; the control plane is simply absent.
Fix:Run three managers in anything you care about. Recovering from lost quorum means
--force-new-clusteron a surviving manager, which is a repair, not a routine.bash # last resort on a surviving manager - re-forms a single-manager clusterdocker swarm init --force-new-cluster