Skip to main content
RunBook Academy

Docker & ContainersXXXVII Β· Orchestration TransitionThe orchestrators

Docker Swarm at a glance β€” what you get and what it costs

Advanced⏱ ~24 mindocker

What you'll learn

  • Describe the Swarm object model and how it maps onto a Compose file
  • Reason about manager quorum and what happens when you lose it
  • Identify the rolling-update defaults that reduce capacity or hide failure
  • Weigh the ecosystem risk of choosing Swarm against its operational simplicity

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11

Not yet marked complete on this device.

Swarm mode is built into the Docker Engine you already run. There is no separate control plane to install, no new CLI, and no new file format β€” docker swarm init turns a host into a one-node cluster and docker stack deploy takes a Compose file.

That makes it the cheapest possible step from single-host Compose to multi-host, and the cheapness is the entire argument for it. This lesson covers what you actually get, the defaults worth changing on day one, and the risk you take on by choosing it.

The object model

Configuration changebootstrap
# On the first manager
docker swarm init --advertise-addr 192.0.2.11

# Read the join tokens on the manager
docker swarm join-token manager
docker swarm join-token worker

# On each additional node, with the token printed above
TOKEN=REPLACE_ME
docker swarm join --token "$TOKEN" 192.0.2.11:2377
Read-only / Safenode ls
$ docker node ls
ID          HOSTNAME   STATUS   AVAILABILITY   MANAGER STATUS   ENGINE VERSION
p2x9k1v...  node01     Ready    Active         Leader           28.3.2
q7m4b8n...  node02     Ready    Active         Reachable        28.3.2
r5t1w6z...  node03     Ready    Active         Reachable        28.3.2
s9c3h2j...  node04     Ready    Active                          28.3.2

Illustrative output

Quorum is the thing to understand first

Managers keep the desired state in a Raft log. Writes require a majority: with N managers, quorum is (N / 2) + 1.

ManagersQuorumFailures tolerated
110
321
532
743

Use an odd number. Going from 3 to 4 managers does not improve fault tolerance β€” both tolerate one failure β€” and it adds a node to every consensus round. Beyond 7, the write latency of consensus starts to cost more than the extra redundancy is worth.

Compose is nearly, but not entirely, the same file

docker stack deploy -c compose.yml myapp accepts a Compose file, and the differences are the source of most first-week frustration:

Compose keyIn a stack
deploy:Ignored by docker compose, used by docker stack deploy
build:Ignored β€” the image must already be in a registry every node can reach
depends_on:Accepted, but the condition: forms are ignored; there is no startup ordering
restart:Ignored β€” use deploy.restart_policy
container_name:Ignored β€” task names are generated
ports:Published through the routing mesh unless you set mode: host
env_file:Read on the machine running the deploy, not on the nodes

The second difference nobody expects: there is no startup ordering. Swarm starts every service at once and relies on applications retrying their dependencies. An application that exits when its database is not yet reachable will crash-loop through the first minute of every deploy. If you cannot fix the application, its entrypoint needs to wait and retry.

Defaults worth changing on day one

services:
  web:
    image: registry.example.com/myorg/myapp:1.4.2
    deploy:
      replicas: 4
      update_config:
        parallelism: 1
        delay: 60s
        order: start-first
        failure_action: rollback
      rollback_config:
        parallelism: 0
        order: stop-first
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
      resources:
        limits:
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Note reservations versus limits: the scheduler places tasks using reservations, and the kernel enforces limits. A service with limits and no reservations can be scheduled onto a node that cannot actually satisfy it, and you find out when the OOM killer does.

The routing mesh, and the source IP

By default a published port is reachable on every node in the swarm, even nodes not running a task for that service. The ingress network forwards the request to a node that is.

That is convenient β€” any node’s address works as a load balancer target β€” and it has a cost: by the time the request reaches your container, the source address is the ingress network’s, not the client’s. Access logs show internal addresses. Rate limiting by IP does not work. Geo-lookup does not work.

Two ways out, both with trade-offs:

  • mode: host publishing. The port is bound only on nodes running a task, and the real source IP is preserved. You lose the β€œany node works” property, so your external load balancer must know which nodes are running the service.
  • A reverse proxy at the edge that sets X-Forwarded-For, with the application configured to trust it. Standard practice, and the usual answer.

The honest costs

Against those: a Swarm cluster is three Docker hosts and a docker swarm init. There is no separate control plane to patch, no CNI plugin to choose, no ingress controller to operate, and no quarterly upgrade treadmill. For a team of two running a dozen services, that difference is not small.

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. A five-manager swarm loses three managers. What happens?

  2. Q2. The `deploy:` section of a Compose file is ignored by `docker compose up` but used by `docker stack deploy`.

  3. Q3. Which Swarm defaults are worth changing for a production service? Select all that apply.

  4. Q4. Why do access logs from a service published through the Swarm routing mesh show internal addresses instead of client IPs?

Passing score: 75%. Answers are checked in this browser.