55.9 Swarm stacks: secrets, configs and docker stack deploy
Friends, typing docker service create for every service does not scale. A stack is a Compose file deployed to a swarm: web, API, database, secrets and configs, all in one file, with one command. Today, step by step: the deploy section, docker stack deploy, Swarm secrets that are encrypted and mounted only where needed, configs for nginx files, rotating a secret, and updating the whole stack.
मित्रांनो, प्रत्येक service साठी docker service create टाइप करणं वाढत्या कामाला पुरत नाही. Stack म्हणजे swarm वर deploy केलेली Compose file: web, API, database, secrets आणि configs, सगळं एका file मध्ये, एका command ने. आज step by step: deploy section, docker stack deploy, encrypted आणि फक्त गरजेच्या ठिकाणी mount होणारे Swarm secrets, nginx files साठी configs, secret rotate करणं, आणि पूर्ण stack update करणं.
दोस्तों, हर service के लिए docker service create टाइप करना बढ़ते काम में नहीं चलता. Stack यानी swarm पर deploy की गई Compose file: web, API, database, secrets और configs, सब एक file में, एक command से. आज step by step: deploy section, docker stack deploy, encrypted और सिर्फ़ ज़रूरी जगह mount होने वाले Swarm secrets, nginx files के लिए configs, secret rotate करना, और पूरा stack update करना.
1. Start from a story
After moving to Swarm, Amit at Nykaa still had a problem. The database password sat in a .env file copied to every server by hand, and nginx.conf was baked into a custom image that had to be rebuilt for every small change.
He wrote one stack file. The password became a Swarm secret: encrypted in the managers' Raft log, sent only to nodes running the API and database, and mounted as a file in memory under /run/secrets. nginx.conf became a Swarm config, mounted read-only into the stock nginx image.
Now a release is one command, docker stack deploy. Changing nginx is a new config version and a rolling update, no image rebuild. Rotating the password is a new secret version and two service updates.
Today we build Amit's stack with fake values like change-me.
2. Secrets step by step
Step 1: Create a Swarm secret
Why: Encrypted and stored by the managers.
What: docker secret create reads the value from a file or stdin. Use printf so no newline is added, and never put the value in shell history.
Example 1: From stdin
Type this:
printf 'change-me' | docker secret create db_password_v1 -
You will see:
q9w8e7r6t5y4...
What it means: The id of the secret.
Example 2: List
Type this:
docker secret ls --format '{{.Name}} {{.CreatedAt}}'
You will see:
db_password_v1 2026-10-25 06:55:10 +0000 UTC
What it means: That is 12:25 PM IST.
Example 3: Cannot read it back
Type this:
docker secret inspect -f '{{.Spec.Data}}' db_password_v1
You will see:
[]
What it means: The value is never shown, not even to admins.
Step 2: Use a secret in a service
Why: Mounted as a file in memory.
What: Give the service --secret. The file appears at /run/secrets/NAME on a tmpfs, only on nodes running that service.
Example 4: Postgres with a secret
Type this:
docker service create --name db --secret source=db_password_v1,target=db_password -e POSTGRES_PASSWORD_FILE=/run/secrets/db_password postgres:16-alpine >/dev/null && sleep 8 && docker service ls --format '{{.Name}} {{.Replicas}}' | grep db
You will see:
db 1/1
What it means: target gives a stable file name.
Example 5: Look inside
Type this:
docker exec $(docker ps -q -f name=db.1) sh -c 'ls -l /run/secrets; mount | grep secrets'
You will see:
-r--r--r-- 1 root root 9 db_password
tmpfs on /run/secrets/db_password type tmpfs (ro,relatime)
What it means: In memory, read-only.
Example 6: Not in env
Type this:
docker service inspect -f '{{.Spec.TaskTemplate.ContainerSpec.Env}}' db
You will see:
[POSTGRES_PASSWORD_FILE=/run/secrets/db_password]
What it means: Only the path.
Step 3: Secret file permissions and owner
Why: Let a non-root app read it.
What: Set uid, gid and mode on the secret mount so the app user can read it and nobody else.
Example 7: Set owner
Type this:
docker service create --name reader --secret source=db_password_v1,target=pw,uid=10001,gid=10001,mode=0400 --user 10001 alpine:3.20 sh -c 'cat /run/secrets/pw; sleep 1d' >/dev/null && sleep 3 && docker service logs reader 2>&1 | tail -1
You will see:
reader.1.x1y2@mgr1 | change-me
What it means: The non-root user can read it.
Example 8: Mode
Type this:
docker exec $(docker ps -q -f name=reader.1) ls -l /run/secrets/pw
You will see:
-r-------- 1 10001 10001 9 pw
What it means: 0400, owner only.
Example 9: Remove the demo
Type this:
docker service rm reader db >/dev/null && echo removed
You will see:
removed
What it means: The stack will recreate them.
3. Configs step by step
Step 4: Create a Swarm config
Why: Non-secret files like nginx.conf.
What: A config is like a secret but not encrypted, for files that are not sensitive.
Example 10: Write nginx.conf
Type this:
printf '%s\n' 'server {' ' listen 8080;' ' location / { proxy_pass http://api:5000; }' ' location /health { return 200 "ok";}' '}' > nginx.conf && docker config create nginx_conf_v1 nginx.conf
You will see:
r1t2y3u4...
What it means: Stored in the swarm.
Example 11: Read it back
Type this:
docker config inspect --pretty nginx_conf_v1 | tail -6
You will see:
Data:
server {
listen 8080;
location / { proxy_pass http://api:5000; }
location /health { return 200 "ok";}
}
What it means: Configs can be read; never put secrets in them.
Example 12: List
Type this:
docker config ls --format '{{.Name}}'
You will see:
nginx_conf_v1
What it means: Versioned by name.
Step 5: Configs are immutable
Why: Change means a new version.
What: You cannot edit a config or secret. Create v2 and point the service at it.
Example 13: Try to update
Type this:
docker config update nginx_conf_v1 2>&1 | head -1
You will see:
docker: 'config update' is not a docker command.
What it means: No edit command exists.
Example 14: New version
Type this:
sed -i 's/listen 8080;/listen 8080;\n gzip on;/' nginx.conf && docker config create nginx_conf_v2 nginx.conf
You will see:
p0o9i8u7...
What it means: v2 is a new object.
Example 15: Remove only when unused
Type this:
docker config rm nginx_conf_v1 2>&1 | tail -1
You will see:
nginx_conf_v1
What it means: Fails if a service still uses it.
4. The stack file step by step
Step 6: The deploy section
Why: Swarm keys inside Compose.
What: deploy holds replicas, update and rollback settings, restart policy, resources and placement. docker compose ignores most of it; Swarm uses it.
Example 16: Write the API part
Type this:
printf '%s\n' 'services:' ' api:' ' image: registry.example.com/shop/api:1.4.0' ' environment:' ' DB_HOST: db' ' DB_PASSWORD_FILE: /run/secrets/db_password' ' secrets:' ' - source: db_password_v1' ' target: db_password' ' networks: [backend]' ' deploy:' ' replicas: 3' ' update_config: {parallelism: 1, delay: 10s, order: start-first, failure_action: rollback}' ' rollback_config: {parallelism: 1}' ' restart_policy: {condition: on-failure, max_attempts: 5}' ' resources:' ' limits: {cpus: "0.5", memory: 256M}' > stack.yaml && echo part1
You will see:
part1
What it means: Replicas, safe updates, limits.
Example 17: Add web and db
Type this:
printf '%s\n' ' web:' ' image: nginx:1.27-alpine' ' ports: ["80:8080"]' ' configs:' ' - source: nginx_conf_v2' ' target: /etc/nginx/conf.d/default.conf' ' networks: [frontend, backend]' ' deploy: {replicas: 2}' ' db:' ' image: postgres:16-alpine' ' environment: {POSTGRES_PASSWORD_FILE: /run/secrets/db_password}' ' secrets: [{source: db_password_v1, target: db_password}]' ' volumes: [pgdata:/var/lib/postgresql/data]' ' networks: [backend]' ' deploy:' ' replicas: 1' ' placement: {constraints: [node.labels.db == true]}' >> stack.yaml && echo part2
You will see:
part2
What it means: The db stays on one labelled node with its volume.
Example 18: Top-level blocks
Type this:
printf '%s\n' 'networks:' ' frontend: {driver: overlay}' ' backend: {driver: overlay, internal: true}' 'volumes: {pgdata: {}}' 'secrets:' ' db_password_v1: {external: true}' 'configs:' ' nginx_conf_v2: {external: true}' >> stack.yaml && docker stack config -c stack.yaml >/dev/null && echo valid
You will see:
valid
What it means: external means already created. backend has no internet access.
Step 7: docker stack deploy
Why: One command for everything.
What: Label the database node, then deploy. Every service gets the stack name as a prefix.
Example 19: Label and deploy
Type this:
docker node update --label-add db=true wrk1 >/dev/null && docker stack deploy -c stack.yaml --with-registry-auth shop
You will see:
Creating network shop_frontend
Creating network shop_backend
Creating service shop_api
Creating service shop_web
Creating service shop_db
What it means: --with-registry-auth sends your registry login to the nodes.
Example 20: Services
Type this:
docker stack services shop --format '{{.Name}} {{.Replicas}} {{.Image}}'
You will see:
shop_api 3/3 registry.example.com/shop/api:1.4.0
shop_db 1/1 postgres:16-alpine
shop_web 2/2 nginx:1.27-alpine
What it means: All running.
Example 21: Tasks
Type this:
docker stack ps shop --format '{{.Name}} {{.Node}} {{.CurrentState}}' | head -4
You will see:
shop_api.1 mgr1 Running 30 seconds ago
shop_api.2 wrk2 Running 30 seconds ago
shop_db.1 wrk1 Running 30 seconds ago
shop_web.1 wrk2 Running 28 seconds ago
What it means: db is on wrk1 as constrained.
Step 8: Service discovery on overlay
Why: Names work across nodes.
What: Services on the same overlay network reach each other by service name, wherever the tasks run.
Example 22: DNS
Type this:
docker exec $(docker ps -q -f name=shop_web.1) nslookup api 2>/dev/null | tail -2
You will see:
Name: api
Address: 10.0.2.5
What it means: A virtual IP for the api service.
Example 23: Call the API
Type this:
curl -s localhost/health
You will see:
ok
What it means: nginx config from the Swarm config.
Example 24: Isolation
Type this:
docker exec $(docker ps -q -f name=shop_db.1) wget -q -T3 -O- example.com 2>&1 | tail -1
You will see:
wget: bad address 'example.com'
What it means: The internal backend network has no internet.
Step 9: Update the stack
Why: Edit the file, deploy again.
What: Running docker stack deploy again applies only the differences, as rolling updates.
Example 25: New API version
Type this:
sed -i 's|shop/api:1.4.0|shop/api:1.5.0|' stack.yaml && docker stack deploy -c stack.yaml --with-registry-auth shop | tail -1
You will see:
Updating service shop_api (id: a1b2...)
What it means: Only api changes.
Example 26: Watch
Type this:
docker service ps shop_api --format '{{.Name}} {{.Image}} {{.CurrentState}}' | head -3
You will see:
shop_api.1 registry.example.com/shop/api:1.5.0 Running 5 seconds ago
\_ shop_api.1 registry.example.com/shop/api:1.4.0 Shutdown 6 seconds ago
shop_api.2 registry.example.com/shop/api:1.4.0 Running 2 minutes ago
What it means: One at a time, 10 seconds apart.
Example 27: Roll back a service
Type this:
docker service rollback shop_api | tail -1
You will see:
verify: Service shop_api converged
What it means: Back to 1.4.0 if 1.5.0 misbehaves.
Step 10: Rotate a secret
Why: New version, rolling update.
What: Create v2, switch the service to it, then remove v1. Keep the same target so the app path does not change.
Example 28: Create v2
Type this:
printf 'change-me-2' | docker secret create db_password_v2 -
You will see:
z5x4c3v2...
What it means: Fake new value.
Example 29: Switch the service
Type this:
docker service update --secret-rm db_password_v1 --secret-add source=db_password_v2,target=db_password shop_api | tail -1
You will see:
verify: Service shop_api converged
What it means: Tasks restarted with the new file. Change the database password itself first.
Example 30: Update the file and clean up
Type this:
sed -i 's/db_password_v1/db_password_v2/g' stack.yaml && docker stack deploy -c stack.yaml shop >/dev/null && docker secret rm db_password_v1
You will see:
db_password_v1
What it means: The stack file matches reality again.
Step 11: Healthchecks and environment-specific files
Why: Safe updates and one file per environment.
What: Add a healthcheck so rolling updates wait for healthy tasks, and keep production differences in an override file merged by docker stack config.
Example 31: Healthcheck in the file
Type this:
printf '%s\n' 'services:' ' api:' ' healthcheck:' ' test: ["CMD", "wget", "-qO-", "http://localhost:5000/health"]' ' interval: 10s' ' retries: 3' > stack.health.yaml && echo written
You will see:
written
What it means: Merged with the main file.
Example 32: Production override
Type this:
printf '%s\n' 'services:' ' api:' ' deploy: {replicas: 6}' > stack.prod.yaml && docker stack config -c stack.yaml -c stack.health.yaml -c stack.prod.yaml | grep -A1 'replicas' | head -2
You will see:
replicas: 6
What it means: The last file wins.
Example 33: Deploy merged
Type this:
docker stack deploy -c stack.yaml -c stack.health.yaml -c stack.prod.yaml --with-registry-auth shop | tail -1
You will see:
Updating service shop_db (id: e5f6...)
What it means: Same app, production size.
5. Run and remove a stack
Step 12: Logs, inspect and removal
Why: Everyday stack commands.
What: Read logs for a whole service, inspect one, and remove the stack without losing volumes.
Example 34: Service logs
Type this:
docker service logs --tail 2 shop_web
You will see:
shop_web.1.ab12@wrk2 | 10.0.0.2 - - "GET /health HTTP/1.1" 200 2
shop_web.2.cd34@mgr1 | 10.0.0.2 - - "GET /health HTTP/1.1" 200 2
What it means: Merged from every task.
Example 35: Remove the stack
Type this:
docker stack rm shop
You will see:
Removing service shop_api
Removing service shop_db
Removing service shop_web
Removing network shop_backend
Removing network shop_frontend
What it means: Services and networks only.
Example 36: Volumes survive
Type this:
docker volume ls --format '{{.Name}}' | grep pgdata
You will see:
shop_pgdata
What it means: The database data is still there, on wrk1.
6. Common mistakes and how Amit avoids them
Mistake one: passwords in environment: in the stack file. Amit uses secrets with _FILE variables.
Mistake two: editing nginx.conf and expecting a change. Configs are immutable; he creates a new version.
Mistake three: a database with 3 replicas. Postgres is not a cluster by itself; he runs one replica pinned to a labelled node, or uses Amazon RDS.
Mistake four: forgetting --with-registry-auth, so nodes cannot pull private images.
Mistake five: deleting a secret still in use. He switches services to v2 first, then removes v1.
Check yourself
Q1. Where does a Swarm secret appear inside a task?
As a file under /run/secrets on an in-memory filesystem.
Q2. Can you edit a config?
No; create a new version and update the service.
Q3. What does docker stack deploy do on a second run?
Applies only the differences as rolling updates.
Q4. Does docker stack rm delete volumes?
No; named volumes stay.
Friends, one stack file holds services, deploy settings, secrets, configs and networks. Deploy it with docker stack deploy, version secrets and configs by name, and rotate by adding v2 before removing v1. Did you understand? Let us try it once more. Deploy the shop stack on a one-node swarm, change the nginx config to v2, and rotate the database secret.
मित्रांनो, एका stack file मध्ये services, deploy settings, secrets, configs आणि networks. docker stack deploy ने deploy करा, secrets आणि configs ला नावाने version द्या, आणि v1 काढण्याआधी v2 जोडून rotate करा. समजलं का? चला, पुन्हा एकदा करून पाहू. एका node च्या swarm वर shop stack deploy करा, nginx config v2 करा, आणि database secret rotate करा.
दोस्तों, एक stack file में services, deploy settings, secrets, configs और networks. docker stack deploy से deploy कीजिए, secrets और configs को नाम से version दीजिए, और v1 हटाने से पहले v2 जोड़कर rotate कीजिए. समझ आया? चलिए, एक बार फिर करके देखते हैं। एक node के swarm पर shop stack deploy कीजिए, nginx config v2 कीजिए, और database secret rotate कीजिए.
Ravindra Bagale's Tip
Put the version in every secret and config name, like db_password_v2. It makes rotation a normal release instead of a scary manual edit.
प्रत्येक secret आणि config च्या नावात version ठेवा, जसं db_password_v2. त्यामुळे rotation हे भीतीदायक manual काम न राहता साधं release होतं.
हर secret और config के नाम में version रखिए, जैसे db_password_v2. इससे rotation डरावना manual काम न रहकर सामान्य release बन जाता है.
Lab: try at home
- Create db_password_v1 with printf and confirm it cannot be read back.
- Run Postgres as a service using the secret file.
- Create nginx_conf_v1 and v2 configs.
- Write stack.yaml with deploy, secrets, configs and two overlay networks.
- Deploy it with docker stack deploy and check services and tasks.
- Update the API tag and roll it back.
- Rotate the secret to v2 and remove v1.
Recap
- A stack is a Compose file for a swarm.
- deploy holds replicas, updates, limits and placement.
- Secrets are encrypted and mounted in /run/secrets.
- Configs hold non-secret files and are immutable.
- docker stack deploy applies differences as rolling updates.
- Rotate by versioned names; stack rm keeps volumes.
Revision card
Interview card