Ravindra BagaleCourses & study guides मराठी

Chapter 55: Docker Security Best Practices, Image Scanning with Trivy, Logging, Monitoring and Docker Swarm Basics

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.

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.

stack.yamlservices, secrets, configs, networksdocker stack deploysent to the managerManager stores statesecrets encrypted in RaftTasks on nodessecrets in /run/secrets, configs as filesOverlay networkservices find each other by name
Picture 1: one stack file to a running app

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

Edit stack.yamlnew tag, config or secret versiondocker stack configcheck the merged filedocker stack deployonly differences appliedRolling update with healthcheckone task at a timeProblemdocker service rollback
Picture 2: a release with 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.

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.

Lab: try at home

  1. Create db_password_v1 with printf and confirm it cannot be read back.
  2. Run Postgres as a service using the secret file.
  3. Create nginx_conf_v1 and v2 configs.
  4. Write stack.yaml with deploy, secrets, configs and two overlay networks.
  5. Deploy it with docker stack deploy and check services and tasks.
  6. Update the API tag and roll it back.
  7. 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.

Ravi Sir's Cards for this lesson

मराठी cards