The rollout that parked itself at 50%
A swarm has three managers and two workers. Service checkout runs 8 replicas of checkout:1, published on port 443. Image checkout:2 returns 503 on /healthz for about 25 seconds after process start, then 200. The service was created with --health-cmd "curl -fsS http://localhost:8080/healthz" --health-interval 5s --health-retries 3 and otherwise default update settings. Running docker service update --image checkout:2 stalls: docker service ps shows new tasks cycling starting → running → Rejected, and the service sits at 4 tasks on v1 and 4 on v2. docker service rollback checkout also pauses, this time with v1 tasks rejected.
1. Diagnose the situation: why did the forward update pause, and why did the rollback pause too? Name the specific parameters and defaults responsible.
Read the worked answer
What swarm does during an update
docker service updatereplaces tasks under the service'sUpdateConfig. Defaults:parallelism: 1,delay: 0s,order: stop-first,monitor: 5s,failure_action: pause.- A replacement task must reach
Runningand pass its health check. A task that fails--health-retries(3) at--health-interval(5s) is killed and retried.
Why v2 was rejected
--health-start-perioddefaults to0s, so the probe counts from the first second of container life.checkout:2returns 503 for ~25s. The task gets 3 × 5s = 15s of grace, is declared unhealthy, restarted, and never converges.docker service ps --no-truncshowsRejected/task: non-zero exitentries.- Swarm hits the failure action: pause. That is exactly the 4/4 split observed.
Why the rollback paused as well
- A rollback replays the previous spec through the same machinery - and the spec still carries the aggressive health parameters. Cold-starting v1 tasks under simultaneous restart also miss the 15s window.
- Nothing is wrong with v1 or the registry. The health check is the defect, and it applies in both directions. This is the key judgement: don't debug the image, debug the probe.
Confirm before changing anything
docker service ps --no-trunc checkout | head -20
docker service inspect --format '{{json .UpdateStatus}}' checkout
docker inspect --format '{{json .State.Health}}' <container-id>
docker service logs checkout --tail 50
UpdateStatus.Message will state the update was paused due to failure or early termination of a task.
2. Give the command sequence to converge on v2 without a second outage, and justify the ordering of the changes.
Read the worked answer
One update, not three. Every docker service update starts a new rollout, so bundle the image, the health fix and the update policy into a single invocation:
docker service update \
--image checkout:2 \
--health-cmd "curl -fsS --max-time 3 http://localhost:8080/healthz" \
--health-interval 10s \
--health-retries 3 \
--health-start-period 45s \
--update-order start-first \
--update-parallelism 2 \
--update-delay 20s \
--update-monitor 60s \
--update-failure-action rollback \
checkout
Why each piece
--health-start-period 45s- failures during warm-up no longer count againstretries; 45s > 25s with margin. This alone would have prevented the original hang.--update-order start-first- the new task is scheduled, started and healthy before the old one is removed, so capacity never dips below the current level. Requires a free slot; fine with ingress-published ports.--update-parallelism 2+--update-delay 20s- bounds blast radius and lets the routing mesh converge between waves.--update-monitor 60s- how long swarm watches a task after it is healthy before advancing, so slow failures surface inside the same slot.--update-failure-action rollback- if it still fails, swarm reverts automatically instead of parking half-deployed at 3 a.m.
Verify
docker service ps checkout
docker service inspect --format '{{.UpdateStatus.State}}' checkout # completed
docker service rollback checkout # manual escape hatch
If start-first cannot schedule (no headroom), fall back to stop-first with parallelism 1 - a slower rollout is better than leaving the service split.
3. What should go into the service definition or CI pipeline so the *next* bad release fails fast and visibly instead of hanging halfway?
Read the worked answer
Treat the health check as part of the artifact, not an afterthought.
- Model the start-up reality.
start_periodmust exceed real warm-up (DB migrations, cache fill) with margin;interval×retriesshould be a genuine failure budget, not 15 seconds. Separate liveness (/livez, is the process wedged?) from readiness (/readyz, can it serve?) and gate the update on readiness. - Test the probe in CI. Start the built image, sleep past warm-up, and
curl --failthe health endpoint. A probe that fails on a good image is a release blocker, not a runtime surprise. - Make the policy explicit in the compose/stack file, not left to defaults:
healthcheck:
test: ["CMD", "curl", "-fsS", "--max-time", "3", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 45s
deploy:
update_config:
order: start-first
parallelism: 2
delay: 20s
monitor: 60s
failure_action: rollback
- Add an early-warning layer: a canary service with a separate DNS name or port at 1 replica, promoted only after it stays healthy; or alert on
UpdateStatus.State != completedand on rolling restart counts, so a paused update pages someone within minutes. - Make failure loud:
--restart-condition on-failurewith a--restart-delay, plus a log/metric check on 5xx from/healthz, so a slow-burn regression is caught before the next deploy.