CoursesDocker in depthServices, replicas & scaling

Services, replicas & scaling

The declarative unit of Swarm.

Intermediate12 min · lesson 17 of 30

Set a thermostat to 21 degrees and it stops caring about the details. It never asks you which vents should open or how long the furnace should run. It keeps nudging the room toward the number you picked, and it does that forever. A Docker service works the same way. You declare an image (the packaged filesystem a container starts from) and how many copies you want, and the swarm (a group of Docker machines pooled together so they behave like one big machine) keeps reality matching that number. docker service create is the swarm's version of docker run. Instead of starting one container on the machine sitting in front of you, it hands the whole cluster a goal and lets the cluster decide which machines the copies land on. Each machine in the cluster is called a node, and two modes cover nearly everything you will ever deploy: replicated keeps a fixed count of copies, and global runs exactly one copy on every node.

terminal
$ docker service create --name web --replicas 3 -p 80:80 nginx:1.27
z1y2x3w4v5u6t7s8r9q0p1o2m
overall progress: 3 out of 3 tasks
verify: Service converged
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z1y2x3w4v5u6 web replicated 3/3 nginx:1.27 *:80->80/tcp
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
7a1b2c web.1 nginx:1.27 node-1 Running Running 2 minutes ago
8d3e4f web.2 nginx:1.27 node-2 Running Running 2 minutes ago
9g5h6i web.3 nginx:1.27 node-1 Running Running 2 minutes ago

Look at the REPLICAS column: 3/3. Read it like a score, running over desired. You asked for three and three are up. That fraction is the one number to keep your eye on. Desired is the goal a manager node (the node that makes scheduling decisions for the cluster) wrote down. Running is how many tasks are actually alive this second. A task is the swarm's word for one container plus the standing job of keeping that container alive. The names web.1, web.2 and web.3 are slots, and a slot holds exactly one task at a time. When the two numbers match, the service has converged and the cluster has nothing left to do. When they differ, the cluster is mid-repair, and it keeps placing tasks until they meet.

When running falls behind desired

A node reboots for a kernel patch. Every task it was running dies with it. A manager notices within seconds. Desired still reads three, running has dropped to two, and the scheduler (the part of the manager that decides which node runs what) starts a replacement on a node that is still up. You type nothing. You can trigger that same repair deliberately by draining a node, which is also the polite way to pull a machine out for maintenance instead of dropping its work on the floor.

terminal
$ docker node update --availability drain node-2
node-2
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z1y2x3w4v5u6 web replicated 2/3 nginx:1.27 *:80->80/tcp
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
a1b2c3 web.2 nginx:1.27 node-3 Running Running 4 seconds ago
d4e5f6 \_ web.2 nginx:1.27 node-2 Shutdown Shutdown 5 seconds ago
7a1b2c web.1 nginx:1.27 node-1 Running Running 6 minutes ago
9g5h6i web.3 nginx:1.27 node-1 Running Running 6 minutes ago
$ docker service ls # a few seconds later
ID NAME MODE REPLICAS IMAGE PORTS
z1y2x3w4v5u6 web replicated 3/3 nginx:1.27 *:80->80/tcp

That second web.2 line, the one with the \_ in front of it, is the slot's history. The old task shut down on node-2 and a fresh task for the same slot came up on node-3. The slot name never changed. The container underneath it got swapped. That is the entire mental model for Swarm. A manager runs one loop: compare desired against running, act on the gap, repeat, and never stop. Scaling, rolling updates, a dead node, a command you typed at 2am and regretted by 2:01, they all get settled by that single loop. Once it clicks, nothing the cluster does will surprise you again.

Scaling means moving one number

Traffic climbs and three copies stop being enough. Scaling is one command. You raise the desired count, the scheduler places the new tasks on nodes that still have room, and the swarm's built-in load balancer starts spreading incoming requests across all of them. When the rush fades, set the number back down and the extra tasks stop. You never log into each host, and you never pick which node gets which copy. You move one number and the cluster catches up. The count at the end of the last command shows where the ten copies actually landed, which was the scheduler's decision, not yours.

terminal
$ docker service scale web=10
web scaled to 10
overall progress: 10 out of 10 tasks
verify: Service converged
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
z1y2x3w4v5u6 web replicated 10/10 nginx:1.27 *:80->80/tcp
$ docker service ps web --filter desired-state=running --format '{{.Node}}' | sort | uniq -c
4 node-1
3 node-2
3 node-3

Rolling out a new version without a gap

Shipping a new image should not take the site down, and neither should shipping a broken one. A service update swaps tasks a few at a time rather than all at once, the way a road crew closes a single lane instead of the whole motorway. You choose how many replicas to swap per step and how long to wait between steps, so a bad version has time to show up on your dashboards before it reaches every copy. Then set the failure action to rollback. If a new task crashes or fails its health check, the rollout stops and the service returns to its previous spec, meaning the saved description of what the service should be: image, ports, replica count and the rest. The damage stays inside one step instead of walking across all three replicas. docker service rollback does the same thing on demand, after the fact, when you decide the new version is wrong for reasons no health check caught.

terminal
$ docker service update \
--image registry.internal/web:1.28 \
--update-parallelism 1 --update-delay 15s \
--update-failure-action rollback \
web
web
overall progress: 3 out of 3 tasks
verify: Service converged
$ docker service ps web --format '{{.Name}}\t{{.Image}}\t{{.CurrentState}}'
web.1 registry.internal/web:1.28 Running about a minute ago
web.2 registry.internal/web:1.28 Running about a minute ago
web.3 registry.internal/web:1.28 Running about a minute ago
$ docker service rollback web # revert to the previous spec on demand
web
overall progress: 3 out of 3 tasks
verify: Service converged
Replicated vs global
Service mode
decides how many copies, and where
replicated
--replicas N
N copies wherever they fit; you scale by changing N
global
--mode global
exactly one task per node; grows as the cluster grows
Replicated for app copies you scale on demand. Global for per-host agents that must run on every node.

Some workloads belong on every host rather than in a fixed count. A log shipper. A metrics agent. A security sensor watching for odd process activity. Each of those should already be running on every node, and it should start on its own the day you add another machine next month. That is global mode. The swarm places exactly one task per node and keeps that promise as the cluster grows or shrinks. You cannot scale a global service, because "one on every node" has already answered the question of how many. For a security sensor the difference is the whole point: a replicated agent leaves you blind on whichever hosts it happened to skip, and blind spots are where attackers like to sit.

terminal
$ docker service create --mode global --name node-agent \
--mount type=bind,source=/var/log,target=/var/log,readonly \
registry.internal/log-agent:1.0
x9y8z7w6v5u4t3s2r1q0
overall progress: 3 out of 3 tasks (3 nodes)
verify: Service converged
$ docker service ps node-agent --format '{{.Name}}\t{{.Node}}\t{{.CurrentState}}'
node-agent.k3n1 node-1 Running 30 seconds ago
node-agent.k3n2 node-2 Running 30 seconds ago
node-agent.k3n3 node-3 Running 30 seconds ago
A service can sit below its desired count forever and never complain
docker service ls prints 0/3 or 2/3 with no error beside it, which looks exactly like a service that is still warming up. It may never converge. If a placement constraint (a rule limiting which nodes are allowed to run the task), a resource reservation, or a stuck image pull means no node can accept the task, the scheduler keeps the desired count on the books, parks the task and moves on. The top-level list stays quiet about all of it. Run docker service ps --no-trunc <name> and read the ERROR column for the real reason, something like "no suitable node (insufficient resources on 3 nodes)" or "no suitable node (scheduling constraints not satisfied)." Any time running lags desired, go to ps. Do not trust ls on its own.

Desired state, checked over and over

A service spec lists the image, the published ports, the networks it joins, the secrets it needs and the replica count. The swarm schedules tasks onto nodes from that spec and replaces failed ones until running matches desired again. Scaling is an edit to the count. Rollback is an edit back to an older version of the same spec. Both are the same operation wearing different clothes. Nobody places containers by hand on individual hosts.

Placement constraints, restart policies and update parallelism decide your blast radius on deploy day. Many replicas buy availability, cost more money and make draining connections cleanly harder. Few replicas are cheap and fragile. Always set resource reservations, a floor of CPU and memory the scheduler has to find before it will place a task, so it stops stacking work onto a node until the kernel starts killing processes for running out of memory.

When something looks wrong, read the tasks rather than the service name. A service can show a calm-looking line while sitting at 0/3 because the image will not pull. docker service ps is where the truth lives.

Before you scale or update a service that real users depend on, write three things into the ticket: the replica count and image tag the service had before you touched it, the host you ran the command from, and the exact command that puts it back. For a service that undo is usually docker service rollback plus the old tag, and it belongs in the ticket rather than in somebody's shell history. Paste the docker service ls line you expect on a healthy system too, so the next person can tell converged from stuck without guessing.

Try this

Run these on a lab swarm (Docker 24 or newer is fine). Watch the REPLICAS column move from 3/3 to 5/5, then check which nodes the two new tasks landed on, so you know what a converged service looks like before you put real traffic in front of one.

terminal
$ docker service create --name web --replicas 3 -p 8080:80 nginx:1.27-alpine
v1x2y3z4
$ docker service ls
ID NAME MODE REPLICAS IMAGE
v1x2y3z4 web replicated 3/3 nginx:1.27-alpine
$ docker service scale web=5
web scaled to 5
$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
… web.1 nginx… n1 Running Running 20s ago
# STATUS: READY — replicas reconciled across nodes

Takeaway

Watch the fraction in the REPLICAS column, because it is the only place the swarm tells you whether it agrees with you yet. Change the spec, never the hosts. When the fraction stops moving, go straight to docker service ps and read the ERROR column. Choose replicated when the count is yours to pick and global when the answer is always one per node. And settle update parallelism, delay and failure action before the deploy that goes wrong, not during it.

Quick check
01docker service ls has shown web at 2/3 for the last ten minutes. What is that telling you?
Incorrect — No. 2/3 means running is sitting below desired. A converged service shows the two numbers equal, like 3/3.
Correct — Desired is 3 and running is 2. Ten minutes is far too long for an ordinary reschedule, so run docker service ps --no-trunc web and read the ERROR column to find out why the third task cannot land.
Incorrect — No. Desired is already 3, the bottom number. Scaling changes what you asked for, not the reason a task refuses to be placed.
Incorrect — No. A swarm never builds anything; services deploy images that already exist. The blocker is placement or a pull, not a build.
02You run docker service update --image web:1.28 --update-parallelism 1 --update-delay 15s --update-failure-action rollback web. Halfway through, one of the freshly started tasks fails its health check. What does the swarm do?
Incorrect — No. Rollback as the failure action does not shrug and press on. It stops the rollout.
Correct — Yes. With --update-failure-action rollback, one failing task halts the rollout and restores the previous spec, keeping the damage inside a single step.
Incorrect — No. Rollback returns the service to a spec that was working rather than taking it down and waiting for a human.
Incorrect — No. Rollback actively moves replicas back to the old spec instead of stranding them on the broken image.
03A teammate deploys a log-shipping agent with docker service create --replicas 3 --name log-agent ... on a 3-node swarm and calls it one per node. Two weeks later they add a fourth node and the agent never shows up on it. What went wrong, and what should they have used?
Incorrect — No. A replicated service does not stretch to new nodes on its own, and re-running create is not the mechanism. Global mode is.
Incorrect — No. Replicated never promises one per node. It can stack two replicas on a single host and leave another host bare.
Correct — Global mode exists for per-host agents: one task on every node, including any node you add later.
Incorrect — No. Hand-tracking the node count is the exact chore global mode removes, and --replicas 4 still will not force one copy onto each distinct host.

Related