Swarm: Stack and Secrets — A Complete Deployment

K
Kai··4 min read

The final article ties everything together. In Article 8 we used Compose to run a multi-container app on one machine. Now we take that same idea up to a Swarm cluster with docker stack deploy, add safe password management with secrets, then clean everything up.

Stack: a Compose file for the whole cluster

A stack is a group of services declared in one compose file and deployed onto the swarm with a single command. It uses the exact compose format from Article 8, just adding a deploy: block to declare swarm-specific things (replica count, update policy...).

stack.yaml:

services:
  web:
    image: nginx:alpine
    ports:
      - "9092:80"
    deploy:
      replicas: 3          # 3 replicas, distributed by swarm
  app:
    image: alpine
    command: sh -c "cat /run/secrets/db_password && sleep 600"
    secrets:
      - db_password        # mount the secret into this service
    deploy:
      replicas: 1

secrets:
  db_password:
    external: true         # secret created outside the stack beforehand

The difference from the compose file in Article 8: the deploy: block (replica count, and optionally update_config, restart_policy, placement...). Note build: cannot be used with a stack — swarm needs an image already on a registry so every node can pull it, so you build and push the image first (Articles 4/9), then reference it with image:.

Secrets: don't leave passwords out in the open

Recall Article 9: don't bake passwords into the image or expose them via environment variables (env vars can be read via docker inspect, logs...). Swarm has a safer secret mechanism: the password is stored encrypted in the cluster's Raft log, and is mounted only into the services that need it, as a file in the /run/secrets/ directory.

Create a secret (read from stdin so it isn't stored raw in shell history):

echo "sieu-bi-mat-123" | docker secret create db_password -
docker secret ls

A service that declares it uses the secret (as in stack.yaml) sees it at /run/secrets/db_password. The app reads that file to get the password, instead of reading an environment variable.

Deploying the stack

docker stack deploy -c stack.yaml mystack
Creating network mystack_default
Creating service mystack_web
Creating service mystack_app

Swarm creates a dedicated overlay network for the stack (recall Article 12) and the services. View the stack:

docker stack services mystack
NAME          REPLICAS
mystack_app   1/1
mystack_web   3/3

Verify the secret really is mounted into the app container:

docker exec $(docker ps --filter name=mystack_app -q) cat /run/secrets/db_password
sieu-bi-mat-123

And the web is reachable via the routing mesh (Article 12):

curl http://localhost:9092
HTTP 200

The stack management commands:

docker stack ls                 # the running stacks
docker stack services mystack   # services in the stack
docker stack ps mystack         # tasks of the stack (on which nodes)
docker stack rm mystack         # remove the whole stack

Comparing docker compose and docker stack: same file format, but compose runs on one machine (it reads build:), while stack deploys onto a swarm cluster (it reads deploy:, needs a ready-made image). The same file can serve both, each side reading the part it cares about.

🧹 Full cleanup and leaving the swarm

This is the final article, so let's clean up everything the series created.

Remove the stack and secret:

docker stack rm mystack
docker secret rm db_password

Leave the swarm (the last manager node needs --force):

docker swarm leave --force

Check that swarm mode is off:

docker info --format '{{.Swarm.LocalNodeState}}'
inactive

Clean up the remaining containers/images/networks/volumes from the whole series:

docker system prune -a       # stopped containers, unused images, leftover networks
docker volume prune          # orphaned volumes (careful: data loss)
docker system df             # see how much disk is still used

Series wrap-up

Across 14 articles, you've gone from "what is a container" to orchestrating a multi-service app on a multi-machine cluster:

  • Fundamentals & deep dive (Articles 0–2): containers solve the environment problem; the client/daemon/containerd/runc architecture; and the lowest layer — namespaces, cgroups, the union filesystem.
  • Core practice (Articles 3–9): running and managing containers; images and layers; writing a Dockerfile and build cache; volumes for persistent data; networking and service discovery; Compose for multi-container apps; optimizing images with multi-stage.
  • Docker Swarm (Articles 10–13): cluster architecture and Raft; service, scale, rolling update; overlay network and routing mesh; stack and secrets.

A few recurring principles worth remembering:

  • A container is just an isolated Linux process — no magic, just namespaces + cgroups + union FS.
  • Order your layers wisely — least-changing first, most-changing last, for fast builds and small images.
  • Data you need to keep must live outside the container — use volumes.
  • Declare the desired state and let the system converge — the core mindset of orchestration.

Where to go next

Swarm is a good starting point for orchestration thanks to its simplicity. When you need to go further:

  • Kubernetes: the de facto standard for orchestration at large scale, with more features than Swarm (but far more complex). Understanding Swarm makes Kubernetes easier to learn because many concepts overlap (service, desired-state, rolling update).
  • CI/CD with Docker: automatically build images and deploy on every code push.
  • A private image registry and security scanning in the pipeline.
  • Observability: collecting logs and metrics from containers at cluster scale.

Thanks for following the series to the end. You can now not only use Docker, but understand how it works underneath — that's the difference between typing commands from memory and truly mastering the tool.