# 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.

URL: https://www.matthewswong.com/en/learn/devops-from-zero/docker-swarm-tutorial/

DevOps from zero · Lesson 19 of 25 · Reviewed on 18 October 2026 · By Matthews Wong (https://www.matthewswong.com/en/about/)

> 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.

## 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.

1. **Meet the swarm.** 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.
2. **Declare two replicas.** 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 nginx
   ```

   *Predict:* 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)
3. **Any node answers.** 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:8080
   ```
4. **Scale to three.** docker 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=3
   ```
5. **Roll out v2, one by one.** docker 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 web
   ```

   *Predict:* 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)
6. **A failed task pauses it.** 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 web
   ```
7. **Roll back to v1.** docker 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.

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.

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.

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.

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.

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.

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.

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

`docker-compose.yml:7-11`

```yaml
    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: .env
```

The 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.

`docker-compose.yml:24-28`

```yaml
    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.

`docker-compose.yml:29-42`

```yaml
    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: 20s
```

The 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.

`docker-compose.yml:49-50`

```yaml
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.

## What breaks

- **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 and Kubernetes, as their own docs describe them.

|  | 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 |

## Try it yourself

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.

1. 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.

   ```bash
   docker swarm init
   docker node ls
   ```
2. Create a service with three replicas published on port 8080, see where its tasks run, and request it through the routing mesh.

   ```bash
   docker service create --name web --replicas 3 --publish published=8080,target=80 nginx
   docker service ps web
   curl http://localhost:8080
   ```
3. Change the desired count and watch the REPLICAS column catch up.

   ```bash
   docker service scale web=5
   docker service ls
   ```
4. Roll 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.

   ```bash
   docker service update --image nginx:alpine --update-parallelism 1 --update-delay 10s web
   docker service ps web
   docker service rollback web
   ```
5. Save 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.

   ```bash
   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: 128M
   ```
6. Remove the service so port 8080 is free, deploy the stack, and list its services. The service is named demo_web.

   ```bash
   docker service rm web
   docker stack deploy -c stack.yml demo
   docker stack services demo
   ```
7. Clean 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.

   ```bash
   docker stack rm demo
   docker swarm leave --force
   ```

## Quick quiz

1. **A swarm has three managers. How many can fail before it loses quorum?**
   A. Two
   B. None
   C. One
   Answer: C. Raft needs a majority of (N/2)+1, which is two of three, so the swarm tolerates (N-1)/2 = 1 failed manager.

2. **Your service runs no task on node C. A client connects to node C on the published port. What happens?**
   A. The connection is refused
   B. The routing mesh forwards it to a task on another node
   C. Swarm moves a task to node C first
   Answer: B. Every node accepts connections on a published port, even with no task for that service, and the mesh forwards them to an active task.

3. **This site's compose file says restart: unless-stopped. What does docker stack deploy do with it?**
   A. Ignores it; deploy.restart_policy decides, with condition any by default
   B. Applies it to every task
   C. Turns it into a placement constraint
   Answer: A. The version 3 reference says restart is ignored when deploying a stack in swarm mode. Restarts follow deploy.restart_policy, whose condition defaults to any.

## Think it through

- **Why does a swarm want an odd number of managers?** 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.
- **What does docker service scale web=5 change, and what keeps it true?** 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.
- **Why can the 127.0.0.1 port binding not protect the app in a swarm?** 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.

## Go deeper

- [Docker Swarm: Container Orchestration for Smaller Teams](https://www.matthewswong.com/en/blog/docker-swarm-orchestration/)
- [Docker Swarm vs Kubernetes: A Small Team Load Test Verdict](https://www.matthewswong.com/en/blog/docker-swarm-vs-kubernetes-small-team-load-test/)
- [Docker Swarm Load Testing: Why More Replicas Didn't Fix Throughput](https://www.matthewswong.com/en/blog/docker-swarm-load-testing-replicas-throughput/)
- [Building a Custom CPU Autoscaler for Docker Swarm](https://www.matthewswong.com/en/blog/docker-swarm-custom-autoscaler-cpu/)

## Sources

- [Docker docs: Swarm mode key concepts](https://docs.docker.com/engine/swarm/key-concepts/)
- [Docker docs: Raft consensus in swarm mode](https://docs.docker.com/engine/swarm/raft/)
- [Docker docs: Administer and maintain a swarm](https://docs.docker.com/engine/swarm/admin_guide/)
- [Docker docs: How services work](https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/)
- [Docker docs: Use Swarm mode routing mesh](https://docs.docker.com/engine/swarm/ingress/)
- [Docker docs: Manage swarm service networks](https://docs.docker.com/engine/swarm/networking/)
- [Docker docs: Apply rolling updates to a service](https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/)
- [Docker docs: Compose Deploy Specification](https://docs.docker.com/reference/compose-file/deploy/)
- [Docker docs: Deploy a stack to a swarm](https://docs.docker.com/engine/swarm/stack-deploy/)
- [Docker Compose: legacy Compose file version 3 reference](https://github.com/docker/compose/blob/v1/docs/Compose%20file%20reference%20(legacy)/version-3.md)
- [Docker docs: docker service create](https://docs.docker.com/reference/cli/docker/service/create/)
- [Docker docs: docker service rollback](https://docs.docker.com/reference/cli/docker/service/rollback/)
- [Docker docs: Manage sensitive data with Docker secrets](https://docs.docker.com/engine/swarm/secrets/)
- [Kubernetes docs: Horizontal Pod Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)

---

- HTML version: https://www.matthewswong.com/en/learn/devops-from-zero/docker-swarm-tutorial/
- Bahasa Indonesia: https://www.matthewswong.com/id/learn/devops-from-zero/docker-swarm-tutorial/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
