Docker Swarm Tutorial: Services, Routing Mesh, Rolling Updates
A Docker Swarm tutorial for beginners: managers and Raft, services and replicas, the routing mesh, rolling updates, and a real Compose file as a stack.
By Matthews WongReviewed on
Key takeaway
Docker Swarm is the cluster mode built into Docker Engine: managers keep the cluster state with Raft and schedule tasks, and workers run them. You declare a service with a number of replicas, any node's published port reaches them through the routing mesh, and updates roll out a few tasks at a time. A Compose file deploys as a stack.
You will be able to
- Explain managers, workers, services, tasks and the routing mesh, and why managers come in odd numbers.
- Create, scale, update and roll back a replicated service on a one-node practice swarm.
- Read a Compose file as a stack file: which keys Swarm ignores, which move under deploy, and what changes for ports and volumes.
Step through the diagram
Press Play, or step with the arrows. Every step is written out below the diagram.
One request, three nodes: the routing mesh and a rolling update
A service asks for two replicas and the manager places them on two nodes. A request to the third node's published port still reaches one through the routing mesh, and an update replaces tasks one at a time.
Keys: ← → step · Home / End · K play or pause
Three Docker Engines form one swarm. The manager keeps the cluster state with Raft and runs tasks too, by default. Every node joins the ingress routing mesh.
You declare the desired state: service web, two replicas, published port 8080. The manager assigns one task to itself and one to worker-a. Worker-b gets none.
docker service create --name web --replicas 2 --publish published=8080,target=80 nginxPredict the next step A request reaches worker-b on port 8080, but worker-b runs no task. What happens? (Connection refused · The mesh forwards it to a running task · Swarm first starts a task on worker-b)Every node accepts connections on the published port. Worker-b runs no task, so the routing mesh forwards the request over the ingress network to a task on another node.
curl http://localhost:8080docker service scale changes the desired count, not a container. The manager sees two tasks where three are wanted and schedules the third, here on worker-b.
docker service scale web=3docker service update --image starts a rolling update. With parallelism 1, Swarm stops one task, starts its replacement on the new image, waits the delay, then moves on.
docker service update --image nginx:alpine --update-parallelism 1 --update-delay 10s webPredict the next step The next new task fails its health check. With the default failure action, what does Swarm do? (Pauses the update · Rolls back on its own · Skips that task and carries on)A container that fails its health checks ends its task. When a task fails mid-update, the default failure action, pause, stops the rollout right there.
docker service ps webdocker service rollback returns the service to the spec it had before the last update, so every task runs v1 again. Setting failure_action to rollback does this for you.
docker service rollback web
What is Docker Swarm, and why do managers come in odd numbers?
Docker Swarm is the cluster mode that ships inside Docker Engine, so you install no extra orchestration software. A swarm is several Docker hosts running in swarm mode, and each one is a node. Manager nodes do the orchestration and cluster management and elect one leader among themselves. Worker nodes run the tasks the managers hand them. By default a manager runs tasks too, which is why a one-machine swarm is enough to practise on.
Read more (2)Show less
Managers keep one consistent copy of the cluster state with the Raft consensus algorithm. Raft needs a majority of (N/2)+1 managers to agree, so it tolerates (N-1)/2 failures: three managers survive losing one, five survive losing two. Docker's admin guide asks for an odd number, because a fourth manager tolerates no more failures than three, and an odd count makes it more likely that quorum survives a network split. Each extra manager also reduces write performance, since more of them must acknowledge every change.
Losing quorum does not take your app down: tasks already running keep running. What stops is change. Until a majority of managers is back, the swarm cannot schedule new tasks, rebalance after a failure, or add, update and remove nodes. One manager is fine on a laptop. In production that one machine is the whole control plane, and the admin guide calls scaling down to a single manager unsafe and not recommended.
What are services, tasks and replicas?
On a swarm you do not start containers by hand. You create a service, the definition of what should run, and Swarm turns it into tasks. A task is a slot the scheduler fills with exactly one container. A replicated service runs the number of tasks you ask for; a global service runs one task on every node.
Read more (2)Show less
Swarm is declarative. The manager keeps comparing the desired state with what actually runs and fixes the difference: if a task fails, the orchestrator removes it and its container and creates a new task. docker service scale web=5 only changes the desired number, exactly like docker service update --replicas=5 web.
Nothing changes that number for you. Docker lists scaling as declaring a task count, not as autoscaling. On the Java Spring Boot platform I load tested on Swarm for my thesis, every service sat at one replica until someone ran the scale command, so I wrote a small CPU-based daemon to do it. The same tests showed what replicas cannot do: one to two replicas cut p95 latency from 8.40 to 4.50 seconds, but throughput only moved from 1.72 to 1.88 iterations per second, because every request waited on one locked database row.
How does the routing mesh send a request to the right node?
When a service publishes a port, every node in the swarm accepts connections on it, including nodes that run no task for that service. The routing mesh forwards each request over the ingress network, a special overlay network, to an active task wherever it runs. That is why an external load balancer can point at any node, or at all of them. If you publish no port number, the manager picks one in the 30000-32767 range.
Read more (2)Show less
The mesh listens on the published port on every address the node has. On an address that is reachable from outside, the port is open to the outside; on the others, only from within the host. To bind a task to its own node's port instead, publish with mode=host, which bypasses the mesh and leaves the load balancing to you.
Services that only talk to each other publish nothing. Attach them to a user-defined overlay network, created with docker network create --driver overlay, and they reach each other by service name. Between the nodes, open 2377/tcp for traffic with and between managers, 7946 over TCP and UDP for node discovery, and 4789/udp for overlay traffic.
How do rolling updates and rollbacks work?
docker service update --image starts a rolling update. By default the scheduler updates one task at a time: it stops the task, starts a container from the new spec, and once that task reports RUNNING it waits the update delay before the next one. If any task returns FAILED during the update, the update pauses, and docker service update on the same service starts it again.
Read more (2)Show less
In a stack file the same settings live under deploy.update_config: parallelism is how many tasks change at once, delay is the wait between batches, failure_action is continue, rollback or pause, with pause the default, and monitor is how long Swarm watches each updated task for failure. order defaults to stop-first; start-first starts the new task before the old one stops, so the two briefly overlap.
In a swarm a health check does real work. A container that fails its health checks ends its task, the orchestrator replaces it, and during an update that failure is what pauses or rolls back the rollout. docker service rollback returns a service to the configuration it had before the last docker service update, and rollback_config sets how that rollback runs.
What does docker stack deploy do with a Compose file?
docker stack deploy creates or updates a stack, a group of services, from a Compose file, and it must run on a manager. It reads the legacy Compose file version 3 format, and Docker's docs say the current Compose Specification is not compatible with it, so treat the stack file as its own file. Every service, network and volume gets the stack name as a prefix: web in a stack called site becomes site_web.
Read more (1)Show less
It does not build images. The build key is ignored, and because a swarm is many Docker Engines, the image has to come from a registry every node can pull from; --with-registry-auth sends your registry login to the nodes. The rest differs key by key, and the worked example below reads this site's compose file that way.
How do secrets work in a swarm?
Swarm has secrets built in, its answer to passwords sitting in env files. A secret is stored in the managers' Raft log, which is encrypted, and only services granted access to it can read it. Inside the container it appears as a file under /run/secrets, on an in-memory tmpfs mount. Secrets exist only for swarm services, not for standalone containers.
Read more (1)Show less
Create one from standard input with docker secret create db_password -, then list it under a service's secrets key. In a stack file, a top-level secrets entry either points at a local file or declares the secret external, meaning it was created beforehand.
Compose, Swarm or Kubernetes: which one do you need?
Docker Compose runs every service on the current host; it does not use swarm mode to spread services across nodes. This site runs that way, with one VPS, one compose file and nginx in front, and for one server that is enough. This lesson reads that compose file as a stack only to show what would change.
Read more (1)Show less
Swarm fits when you outgrow one host but want to keep the Compose shape and the Docker CLI: replicas across nodes, a routing mesh, rolling updates and secrets, all inside the engine you already run. Kubernetes fits when you need what Swarm does not ship, such as the HorizontalPodAutoscaler, which scales a workload to match demand on a 15-second loop by default. Whichever you pick, find the bottleneck first: when one database row is the limit, no orchestrator raises the ceiling.
The real config behind this lesson
Excerpts from the repository that builds and deploys this website, quoted as they are.
image: ${IMAGE:-ghcr.io/matthews-wong/matthews-porto-nextjs:latest}
# Matches the existing container so deploys UPDATE it in place (no duplicate).
container_name: matthewswong-portfolio
restart: unless-stopped
env_file: .envThe image already comes from a registry, which a swarm needs, but the ${IMAGE} value is filled from a sibling .env file, and the version 3 reference says the .env feature does not work with docker stack deploy. container_name and restart are both ignored in swarm mode: tasks are named after the service, such as web.1, and deploy.restart_policy, whose condition defaults to any, decides restarts. env_file carries no swarm note in that reference.
ports:
# Bind to loopback only — nginx terminates TLS and proxies to this port.
- "127.0.0.1:${APP_PORT:-3002}:3000"
mem_limit: 512m
cpus: "1.0"Two keys that do not carry over. The loopback binding keeps the app behind nginx, but the routing mesh listens on every address of every node, and the version 3 long port syntax has no host address field. In a swarm, publish only the proxy and let it reach the app by name on an overlay network. In version 3, deploy.resources replaced mem_limit, and cpus exists only under deploy.resources.limits, so both move there.
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:3000/robots.txt",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 20sThe version 3 format has the same healthcheck keys, with start_period since 3.4, and in a swarm the check gains a job: a container that fails its health checks ends its task, and a failed task pauses a rolling update. Read the timings with that in mind. Failures in the first 20 seconds do not count, and it takes three failures in a row, 30 seconds apart, to mark a container unhealthy, so give update_config's monitor enough time to see it.
volumes:
next-image-cache:A named volume with the default local driver is scoped to one node. In a swarm, each node where a task lands creates its own copy if it is missing, and a task moved to another node starts with a different one. For this image cache that is harmless. For data every replica must share, use a multi-host volume driver or pin the service to one node with a placement constraint.
Mistakes that cost real time
Two managers are worse than one
A majority of two managers is two, so two managers tolerate no failures, exactly like one, and give you two machines that can break quorum. Four tolerate one, the same as three. Run one manager for practice, and three or five when the control plane must survive a lost machine.
A loopback-only port becomes a public one
Under Compose, 127.0.0.1 in the port mapping kept the app reachable only through nginx. A swarm-published port goes through the routing mesh, which listens on every address of every node, so on an externally routable address the app is open to the outside. Publish only the proxy, keep the app on an overlay network, and firewall the nodes.
The other nodes cannot find the image
docker stack deploy ignores build, so an image you built on the manager exists only there. Tasks scheduled on other nodes have nothing to pull. Push the image to a registry every node can reach before deploying, and pass --with-registry-auth when that registry is private.
A paused update looks like a stuck deploy
With the default failure_action, one failed task pauses the rollout, and some tasks stay on the old image while others run the new one. Nothing moves until you act. Read docker service ps to see which task failed and its ERROR column, then either fix and run docker service update again or run docker service rollback.
The data stayed on the old node
The default local volume driver is scoped to one node. When a task is rescheduled elsewhere, Docker creates a new volume of the same name there, without the old data, and the app looks as if it lost it. Use a multi-host volume driver, or a placement constraint that keeps the service on the node that has the volume.
| Docker Compose | Docker Swarm | Kubernetes | |
|---|---|---|---|
| Where it runs | Every service on the current host | A cluster of Docker Engines, the swarm | A cluster of nodes run by a control plane |
| What you install | Docker and Compose | Nothing extra: swarm mode is in Docker Engine | API server, etcd, scheduler, controller manager, kubelet on each node |
| Config format | The Compose Specification | Legacy Compose file version 3, via docker stack deploy | Kubernetes objects, such as a Deployment |
| Cluster state | None: one host | Raft log on the managers; 3 tolerate 1 failure, 5 tolerate 2 | etcd, the store for all API server data |
| Scaling | Containers on one host only | Declared replica count; no autoscaler | By command, or automatically with the HorizontalPodAutoscaler |
| Fits when | One server is enough, as for this site | A few hosts, and the team already writes Compose files | You need autoscaling or tooling that targets Kubernetes |
Do it on your own machine
You need Docker on one machine. A one-node swarm is enough for every step here, and the last step turns swarm mode off again. Use a practice machine, not a server that runs anything you care about.
Turn this Docker Engine into a one-node swarm and list the nodes. The asterisk marks the node you are connected to. If init says the machine has several addresses, run it again with --advertise-addr followed by the network interface name or the address other nodes would use.
docker swarm init docker node lsCreate a service with three replicas published on port 8080, see where its tasks run, and request it through the routing mesh.
docker service create --name web --replicas 3 --publish published=8080,target=80 nginx docker service ps web curl http://localhost:8080Change the desired count and watch the REPLICAS column catch up.
docker service scale web=5 docker service lsRoll out a new image one task at a time, 10 seconds apart. Run docker service ps while it runs, then roll back to the previous spec.
docker service update --image nginx:alpine --update-parallelism 1 --update-delay 10s web docker service ps web docker service rollback webSave this as stack.yml. Stack deploy reads the version 3 format, and order needs version 3.4 or later; the limits sit under deploy, not in mem_limit.
version: "3.8" services: web: image: nginx:alpine ports: - "8080:80" deploy: replicas: 2 update_config: parallelism: 1 delay: 10s order: start-first resources: limits: memory: 128MRemove the service so port 8080 is free, deploy the stack, and list its services. The service is named demo_web.
docker service rm web docker stack deploy -c stack.yml demo docker stack services demoClean up. A manager needs --force to leave, which the docs reserve for a swarm that will not be used again, as this practice one will not.
docker stack rm demo docker swarm leave --force
Three questions before you move on
Pick an answer to see why it is right or wrong. Nothing is graded, so guess freely; a miss is kept in this browser so the course can ask it again later.
Two questions from earlier lessons
Recalling an older lesson after a gap is what makes it stick. Answer from memory before you look anything up.
Answer in your head, then check
1.Why does a swarm want an odd number of managers?Show the answer
Answer:Raft needs a majority to agree, so an even count adds a machine without adding tolerance: four managers survive one failure, like three. An odd count also makes it more likely that quorum survives a network split.
2.What does docker service scale web=5 change, and what keeps it true?Show the answer
Answer:It changes the desired number of tasks. The manager then adds or removes tasks until five run, and replaces any task that fails. Nothing changes that number on its own; Swarm has no autoscaler.
3.Why can the 127.0.0.1 port binding not protect the app in a swarm?Show the answer
Answer:A published port goes through the routing mesh, which listens on every address of every node, and the version 3 long syntax has no host address field. Publish only the proxy and reach the app by service name on an overlay network.
Related deep dives on this site
Sources
Primary sources checked on the research date. Follow them for the current version.
- Docker docs: Swarm mode key concepts
- Docker docs: Raft consensus in swarm mode
- Docker docs: Administer and maintain a swarm
- Docker docs: How services work
- Docker docs: Use Swarm mode routing mesh
- Docker docs: Manage swarm service networks
- Docker docs: Apply rolling updates to a service
- Docker docs: Compose Deploy Specification
- Docker docs: Deploy a stack to a swarm
- Docker Compose: legacy Compose file version 3 reference
- Docker docs: docker service create
- Docker docs: docker service rollback
- Docker docs: Manage sensitive data with Docker secrets
- Kubernetes docs: Horizontal Pod Autoscaling