Chapter 67: Docker Swarm vs Kubernetes: the Same App on Both, Side by Side
67.2 Deploy the orders app on Swarm: stack file, secrets, health checks, scaling and rolling updates
Why: the problem
Now we put a real app on the Swarm: the orders API from Chapter 66 plus a Postgres database. On Swarm the unit of deployment is a stack, a group of services, networks, volumes, configs and secrets described in one Compose file and deployed with docker stack deploy. If your team already uses docker compose on laptops, this is the shortest road to a cluster, because the file format is almost the same. That is Swarm's biggest selling point, and the reason small teams at agencies and startups still reach for it. Why a stack and not a list of docker service create commands? For the same reason we write Kubernetes YAML instead of typing kubectl run: a file can be reviewed in a pull request, stored in Git and applied again after a disaster. docker stack deploy is idempotent, so running it twice changes nothing, and changing one line in the file updates only the service that changed. What goes into the file? The deploy key is where Swarm lives. It sets replicas, the update_config for rolling updates (parallelism, delay, failure_action and order start-first for zero downtime), a rollback_config, restart_policy, resource limits and reservations, and placement constraints such as node.role == worker. Swarm secrets are encrypted in the Raft log and mounted as files under /run/secrets inside only the services that ask for them, so the database password never appears in an environment listing. A healthcheck tells Swarm when a task is healthy; during an update Swarm waits for health before moving on. How will we run it? We create a secret from the command line with a placeholder value, write the stack file with printf, push the image to a registry every node can reach, deploy, scale to six, roll out version 1.1.0 with start-first ordering while a loop calls the API, break an update on purpose to watch the automatic rollback, and roll back by hand. Every step has a direct Kubernetes twin in the next lesson, so keep your timings: you will compare them in 67.4.
What: every term in plain English
- Stack — Group of services, networks, volumes and secrets deployed from one Compose file.
- docker stack deploy — Creates or updates every object in a stack; safe to run repeatedly.
- deploy.replicas — Desired number of tasks for a service.
- update_config — Rolling update settings: parallelism, delay, order, failure_action, monitor.
- order: start-first — Start the new task before stopping the old one, for zero downtime.
- rollback_config — How Swarm rolls back when an update fails.
- Swarm secret — Encrypted value mounted as a file in /run/secrets.
- Swarm config — Non-secret file distributed to services, like a ConfigMap.
- healthcheck — Command Swarm runs to decide if a task is healthy.
- Placement constraint — Rule such as node.role == worker that limits where tasks run.
How: step by step
- Start a local registry as a Swarm service. Nodes can pull.
- Push orders 1.0.0, 1.1.0 and a broken 1.2.0. Three versions.
- Create the db_password secret. Encrypted in Raft.
- Write stack.yml with printf. One file.
- Deploy the stack. Services converge.
- Call the API through the routing mesh. Orders returned.
- Scale orders to six. Six tasks.
- Roll out 1.1.0 while traffic runs. No failed requests.
- Roll out broken 1.2.0 and watch rollback. Back to 1.1.0.
- Remove the stack. Clean.
Pay attention to how much behaviour is packed into a few lines of the deploy key, because this is where the two tools differ most in philosophy. Swarm gives you sensible defaults and a small number of knobs: you can say how many tasks update at once, how long to wait, which order to use and what to do on failure. Kubernetes splits the same ideas into maxSurge, maxUnavailable, readiness probes, minReadySeconds, progressDeadlineSeconds and a separate rollback command, and lets tools like Argo Rollouts replace the whole strategy. For a ten-person team running a dozen services, the Swarm version is often enough and much easier to explain to a new hire. For a company like Swiggy shipping hundreds of services with canaries and automated analysis, the extra knobs are the point.
Example 1: Registry, images and secret
Goal. Registry, images and secret.
prep.sh
M='docker exec node1 docker'
$M service create --name registry --publish 5000:5000 registry:2
for v in 1.0.0 1.1.0 1.2.0-broken; do
docker exec node1 sh -c "printf 'FROM nginx:1.27-alpine\\nRUN echo $v > /usr/share/nginx/html/version\\n' | docker build -t 127.0.0.1:5000/orders:$v -"
$M push 127.0.0.1:5000/orders:$v
done
printf '%s' 'REPLACE_WITH_DB_PASSWORD' | docker exec -i node1 docker secret create db_password -
$M secret ls
$M secret inspect db_password --format '{{.Spec.Name}} {{.CreatedAt}}'
What each line does
registry service— Every node reaches it at 127.0.0.1:5000 through the routing mesh, so all nodes can pull.Three tags— A good release, a newer release and one we break on purpose; in real life use the orders image from Chapter 66.docker secret create -— Reads the value from stdin, so it never appears in shell history; the value here is a placeholder.secret inspect— Shows metadata only; Swarm never prints a secret value back.Raft encryption— Secrets are stored encrypted on managers and sent only to nodes running a service that uses them.
Green output
$ bash prep.sh
registry
overall progress: 1 out of 1 tasks
The push refers to repository [127.0.0.1:5000/orders]
1.0.0: digest: sha256:1a2b... size: 1568
1.1.0: digest: sha256:3c4d... size: 1568
1.2.0-broken: digest: sha256:5e6f... size: 1568
ID NAME CREATED
q8r7... db_password 2 seconds ago
db_password 2026-10-25 03:10:41 +0000 UTC
Expected result. The cluster now has a registry, three image versions and an encrypted secret. Notice that nothing printed the password.
Example 2: The stack file and the first deploy
Goal. The stack file and the first deploy.
stack.sh
mkdir -p swarm
printf '%s\n' \
'services:' \
' orders:' \
' image: 127.0.0.1:5000/orders:1.0.0' \
' ports: ["8080:80"]' \
' networks: [backend]' \
' secrets: [db_password]' \
' environment: {DB_HOST: db, DB_PASSWORD_FILE: /run/secrets/db_password}' \
' healthcheck: {test: ["CMD", "wget", "-qO-", "http://127.0.0.1/version"], interval: 5s, retries: 3}' \
' deploy:' \
' replicas: 3' \
' resources: {limits: {cpus: "0.50", memory: 128M}, reservations: {memory: 64M}}' \
' update_config: {parallelism: 1, delay: 5s, order: start-first, failure_action: rollback, monitor: 15s}' \
' rollback_config: {parallelism: 2, order: start-first}' \
' restart_policy: {condition: on-failure, max_attempts: 3}' \
' db:' \
' image: postgres:17-alpine' \
' networks: [backend]' \
' secrets: [db_password]' \
' environment: {POSTGRES_PASSWORD_FILE: /run/secrets/db_password}' \
' volumes: [dbdata:/var/lib/postgresql/data]' \
' deploy: {replicas: 1, placement: {constraints: [node.hostname == node3]}}' \
'networks: {backend: {driver: overlay}}' \
'volumes: {dbdata: {}}' \
'secrets: {db_password: {external: true}}' > swarm/stack.yml
docker cp swarm/stack.yml node1:/stack.yml
docker exec node1 docker stack deploy -c /stack.yml shop
sleep 15
docker exec node1 docker stack services shop
docker exec node2 wget -qO- http://127.0.0.1:8080/version
What each line does
Compose format— The same file style as docker compose; the deploy key is what Swarm adds.POSTGRES_PASSWORD_FILE— The official Postgres image reads the password from the secret file, not from an env value.order: start-first— New task starts and becomes healthy before an old one stops.failure_action: rollback— If new tasks fail health within monitor time, Swarm rolls back by itself.placement constraint— Pins the database to node3, because its local volume lives there; this is Swarm's weak spot for storage.
Green output
$ bash stack.sh
Creating network shop_backend
Creating service shop_db
Creating service shop_orders
ID NAME MODE REPLICAS IMAGE PORTS
a1.. shop_db replicated 1/1 postgres:17-alpine
b2.. shop_orders replicated 3/3 127.0.0.1:5000/orders:1.0.0 *:8080->80/tcp
1.0.0
Expected result. One file and one command created a network, two services, a volume and wired in the secret. A Kubernetes version of this needs a Deployment, a Service, a StatefulSet, a Secret and a PVC: more objects, but each can be tuned on its own.
Example 3: Scale, rolling update under load, automatic rollback
Goal. Scale, rolling update under load, automatic rollback.
ops.sh
M='docker exec node1 docker'
$M service scale shop_orders=6
docker exec -d node3 sh -c 'while true; do wget -qO- http://127.0.0.1:8080/version >> /tmp/hits || echo FAIL >> /tmp/hits; sleep 0.2; done'
time $M service update --image 127.0.0.1:5000/orders:1.1.0 shop_orders
docker exec node3 sh -c 'sort /tmp/hits | uniq -c'
$M service update --image 127.0.0.1:5000/orders:1.2.0-broken \
--health-cmd 'wget -qO- http://127.0.0.1/missing' shop_orders || true
$M service inspect shop_orders --format '{{.Spec.TaskTemplate.ContainerSpec.Image}} {{.UpdateStatus.State}}'
$M service rollback shop_orders
$M stack rm shop
What each line does
service scale— Six tasks across three nodes in a few seconds.Traffic loop— Calls the API five times a second during the update and logs any failure.time— Records rollout time for six tasks with parallelism 1 and a 5 second delay.Broken health check— New tasks never get healthy, so failure_action rollback fires.service rollback— Manual rollback to the previous spec, like kubectl rollout undo.
Green output
$ bash ops.sh
shop_orders scaled to 6
overall progress: 6 out of 6 tasks
verify: Service shop_orders converged
real 0m58.4s
212 1.0.0
287 1.1.0
rollback: update rolled back due to failure or early termination of task 7kq...
127.0.0.1:5000/orders:1.1.0 rollback_completed
shop_orders
rollback: manually requested rollback
Removing service shop_db
Removing service shop_orders
Removing network shop_backend
Expected result. Zero FAIL lines during the update, an automatic rollback when the broken release could not pass health, and a manual rollback with one command. Note the 58 second rollout time; we will compare it with Kubernetes.
Every command, step by step (kind lab with kubectl)
-
Registry
docker service create --name registry -p 5000:5000 registry:2What it does: Images.
registry -
Push
docker push 127.0.0.1:5000/orders:1.0.0What it does: Image.
digest: sha256:1a2b... -
Secret
printf '%s' 'REPLACE_WITH_DB_PASSWORD' | docker secret create db_password -What it does: Encrypted.
q8r7... -
Deploy
docker stack deploy -c stack.yml shopWhat it does: Stack.
Creating service shop_orders -
Services
docker stack services shopWhat it does: Status.
shop_orders 3/3 -
Tasks
docker stack ps shopWhat it does: Placement.
shop_orders.1 node1 Running -
Scale
docker service scale shop_orders=6What it does: Six.
scaled to 6 -
Update
docker service update --image 127.0.0.1:5000/orders:1.1.0 shop_ordersWhat it does: Rolling.
converged -
Rollback
docker service rollback shop_ordersWhat it does: Undo.
rollback: manually requested rollback -
Remove
docker stack rm shopWhat it does: Clean.
Removing network shop_backend
When it is red 1
No such image: 127.0.0.1:5000/orders:1.0.0
Cause. Image not in a registry every node can reach.
Fix, step by step
- Push to a registry; Swarm nodes do not share local images.
When it is red 2
secret not found: db_password
Cause. External secret not created before deploy.
Fix, step by step
- docker secret create db_password - first.
When it is red 3
shop_db 0/1: no suitable node (scheduling constraints not satisfied)
Cause. Constraint points at a missing node.
Fix, step by step
- Fix node.hostname or add a label with docker node update --label-add.
When it is red 4
update paused due to failure
Cause. failure_action is pause.
Fix, step by step
- Set failure_action: rollback or run docker service update --rollback.
When it is red 5
Ignoring unsupported options: build
Cause. Swarm cannot build images.
Fix, step by step
- Build and push in CI, then reference the image.
Practice tasks
Easy Task 1: Secret
Why: Create.
What to do: Create db_password from stdin.
- Placeholder value.
Solution
docker secret lsEasy Task 2: Stack
Why: Write.
What to do: Write stack.yml with printf.
- deploy key.
Solution
cat swarm/stack.ymlEasy Task 3: Deploy
Why: Run.
What to do: Deploy the stack.
- 3/3.
Solution
docker stack services shopMedium Task 4: Scale
Why: Six.
What to do: Scale to six.
- service scale.
Solution
docker service lsMedium Task 5: Update
Why: Rolling.
What to do: Update to 1.1.0 under load.
- No FAIL.
Solution
sort /tmp/hits | uniq -cMedium Task 6: Rollback
Why: Auto.
What to do: Deploy the broken tag.
- Rolled back.
Solution
docker service inspect shop_ordersHard Task 7: Manual
Why: Undo.
What to do: Roll back by hand.
- service rollback.
Solution
docker service ps shop_ordersHard Task 8: Urban Company
Why: Main task.
What to do: Booking API stack.
- Zero failures.
Solution
docker stack ps bookingHomework: try at home
For Urban Company's booking API, write a stack with the API (4 replicas, start-first updates, automatic rollback) and Postgres with a secret, deploy it on your Swarm, roll out a new tag under a traffic loop and prove zero failed requests.
Recap
- A stack is a Compose file deployed to Swarm with docker stack deploy.
- The deploy key holds replicas, resources, updates, rollback and placement.
- Swarm secrets are encrypted in Raft and mounted under /run/secrets.
- start-first updates plus health checks give zero-downtime rollouts.
- Local volumes tie stateful services to one node; this is Swarm's weakest area.
FAQ
Q. Can I use the same compose file for docker compose and Swarm?
Mostly. docker compose ignores most of deploy, and Swarm ignores build, depends_on conditions and some other keys. Many teams keep one base file plus a small Swarm override.
Q. How do I store Postgres data safely on Swarm?
Use a volume plugin backed by network storage, such as a CSI or NFS driver, or keep the database outside the cluster on Amazon RDS. Pinning to a node is fine only for labs.
Q. Does Swarm have autoscaling?
No built-in autoscaler. You change replicas yourself or with an external script that reads metrics, unlike the Kubernetes HPA from Chapter 63.
Q. How do I rotate a secret?
Create db_password_v2, update the service with --secret-rm db_password --secret-add db_password_v2, then remove the old secret. Secrets are immutable.
Interview questions
-
How does Swarm do zero-downtime deployments?
Model answer: Rolling updates with start-first ordering and health checks, plus automatic rollback on failure.
-
How are Swarm secrets protected?
Model answer: Encrypted at rest in Raft, sent over mutual TLS only to nodes that need them, mounted in memory under /run/secrets.
-
What is the difference between docker compose and docker stack deploy?
Model answer: Compose runs on one host; stack deploy runs services across a Swarm.
-
What is Swarm's biggest limitation?
Model answer: Stateful storage and the small ecosystem: no operators, no built-in autoscaling, few managed offerings.
Quick quiz
Q1. Which command deploys a Compose file to Swarm?
docker stack deploy -c.
Q2. Where are secrets mounted?
/run/secrets.
Q3. Which order gives zero downtime?
start-first.
Q4. What triggers automatic rollback?
failure_action: rollback with failed health.
Q5. Can Swarm build images?
No.
Samajla ka?