← Back to Observability

Docker Swarm Cheatsheet

Cluster Management, Services, Stacks, Networking, Secrets & Rolling Updates

1. What is Docker Swarm & When to Use It

  • Built-in orchestration in Docker Engine — no extra install
  • Best for: small-medium deployments, already using Docker Compose, want simplicity over features
  • Key concepts: Manager nodes, Worker nodes, Services, Tasks, Stacks, Overlay networks

Swarm vs Kubernetes

AspectDocker SwarmKubernetes
ComplexitySimple, native Docker CLIComplex, steep learning curve
Setup timeMinutesHours/days
ScalingGood for small/mediumBuilt for massive scale
Learning curveLow (if you know Docker)High
Load balancingBuilt-in (routing mesh)Ingress controller needed
Service discoveryBuilt-in DNSCoreDNS + Services
Secrets managementBuilt-inBuilt-in
Best forSmall teams, < 100 nodesLarge orgs, cloud-native
CommunitySmallerMassive ecosystem

Architecture

Manager Node 1 (Leader) ←── Raft Consensus ──→ Manager Node 2
        ↕                                            ↕
   Worker Node 1    Worker Node 2    Worker Node 3
        ↕                ↕                ↕
   [containers]     [containers]     [containers]

Routing Mesh: Any node can receive traffic for any service

2. Cluster Setup & Node Management

# Initialize swarm (on first manager)
docker swarm init --advertise-addr 10.0.0.1

# Get join tokens
docker swarm join-token worker
docker swarm join-token manager

# Join as worker
docker swarm join --token SWMTKN-1-xxx 10.0.0.1:2377

# Join as manager
docker swarm join --token SWMTKN-1-yyy 10.0.0.1:2377

# List nodes
docker node ls

# Promote worker to manager
docker node promote node-2

# Demote manager to worker
docker node demote node-2

# Remove node (on the node itself)
docker swarm leave
docker swarm leave --force  # for managers

# Remove node (from manager)
docker node rm node-3

# Update node availability
docker node update --availability drain node-3    # maintenance mode
docker node update --availability active node-3   # back online
docker node update --availability pause node-3    # no new tasks

# Add labels to nodes
docker node update --label-add env=production node-1
docker node update --label-add disk=ssd node-2
docker node update --label-add region=eu-west node-3

# Inspect node
docker node inspect --pretty node-1

3. Service Management

# Create service
docker service create \
  --name web \
  --replicas 3 \
  --publish 80:80 \
  --network my-overlay \
  --env NODE_ENV=production \
  --mount type=bind,source=/data,target=/app/data \
  --constraint 'node.labels.env==production' \
  --limit-cpu 0.5 \
  --limit-memory 512M \
  --reserve-cpu 0.25 \
  --reserve-memory 256M \
  nginx:latest

# List services
docker service ls

# Inspect service
docker service inspect --pretty web

# Service logs
docker service logs web
docker service logs -f --tail 100 web
docker service logs --since 1h web

# Scale service
docker service scale web=5
docker service scale web=5 api=3 worker=2  # multiple at once

# Update service image (rolling update)
docker service update --image nginx:1.25 web

# Update with rollback config
docker service update \
  --image nginx:1.25 \
  --update-parallelism 1 \
  --update-delay 30s \
  --update-failure-action rollback \
  --update-max-failure-ratio 0.25 \
  --rollback-parallelism 1 \
  --rollback-delay 10s \
  web

# Rollback
docker service rollback web

# Remove service
docker service rm web

# List tasks (containers) of a service
docker service ps web
docker service ps web --no-trunc  # full error messages

# Force rebalance (redeploy all tasks)
docker service update --force web

4. Docker Stack (Compose for Swarm)

# docker-stack.yml
version: "3.8"

services:
  web:
    image: myregistry.com/myapp:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        failure_action: rollback
        order: start-first          # start new before stopping old
      rollback_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      placement:
        constraints:
          - node.role == worker
          - node.labels.env == production
        preferences:
          - spread: node.labels.zone  # spread across zones
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.web.rule=Host(`app.example.com`)"
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
      - backend
    secrets:
      - db_password
      - ssl_cert
    configs:
      - source: nginx_conf
        target: /etc/nginx/nginx.conf
    volumes:
      - app_data:/app/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  db:
    image: postgres:16
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.disk == ssd
    volumes:
      - db_data:/var/lib/postgresql/data
    secrets:
      - db_password
    networks:
      - backend
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

  redis:
    image: redis:7-alpine
    deploy:
      replicas: 1
    networks:
      - backend

networks:
  frontend:
    driver: overlay
    attachable: true
  backend:
    driver: overlay
    internal: true        # no external access

volumes:
  app_data:
    driver: local
  db_data:
    driver: local

secrets:
  db_password:
    external: true
  ssl_cert:
    file: ./certs/ssl.pem

configs:
  nginx_conf:
    file: ./nginx.conf

Stack Commands

# Deploy stack
docker stack deploy -c docker-stack.yml myapp

# List stacks
docker stack ls

# List services in stack
docker stack services myapp

# List tasks in stack
docker stack ps myapp

# Remove stack
docker stack rm myapp

# Update stack (redeploy with changes)
docker stack deploy -c docker-stack.yml myapp  # same command, idempotent

5. Networking

# Create overlay network
docker network create --driver overlay --attachable my-network

# Create encrypted overlay
docker network create --driver overlay --opt encrypted my-secure-net

# Create with subnet
docker network create --driver overlay --subnet 10.10.0.0/24 my-network

# List networks
docker network ls

# Inspect network
docker network inspect my-network

# Service discovery: containers resolve service names via DNS
# Inside container: curl http://web:80 → resolves to service VIP

# Routing mesh: published ports available on ALL nodes
# External LB → any node:80 → routes to correct container

# Bypass routing mesh (host mode):
docker service create --publish mode=host,target=80,published=80 web

Network Architecture

External Traffic
      ↓
[Load Balancer] → Any Swarm Node:published_port
      ↓
[Routing Mesh / Ingress Network]
      ↓
[Overlay Network]
      ↓
[Service VIP] → Task 1, Task 2, Task 3 (round-robin)

6. Secrets & Configs

# Create secret from file
echo "mypassword" | docker secret create db_password -
docker secret create ssl_cert ./cert.pem

# List secrets
docker secret ls

# Inspect secret (metadata only, never shows value)
docker secret inspect db_password

# Remove secret
docker secret rm db_password

# Use secret in service
docker service create --secret db_password --name web myapp
# Secret available at /run/secrets/db_password inside container

# Rotate secret (create new, update service, remove old)
echo "newpassword" | docker secret create db_password_v2 -
docker service update \
  --secret-rm db_password \
  --secret-add db_password_v2 \
  web

# Configs (non-sensitive configuration)
docker config create nginx_conf ./nginx.conf
docker config ls
docker service create --config nginx_conf --name web nginx
# Config available at /nginx_conf inside container (or custom target)

7. Registry & Image Distribution

# Private registry in swarm
docker service create \
  --name registry \
  --publish 5000:5000 \
  --mount type=bind,source=/registry-data,target=/var/lib/registry \
  registry:2

# Tag and push
docker tag myapp:latest 10.0.0.1:5000/myapp:latest
docker push 10.0.0.1:5000/myapp:latest

# Deploy from private registry
docker service create --name web 10.0.0.1:5000/myapp:latest

# With registry auth
docker login myregistry.com
# Docker stores auth in ~/.docker/config.json
# For swarm: use --with-registry-auth
docker service create --with-registry-auth --name web myregistry.com/myapp:latest
docker stack deploy --with-registry-auth -c stack.yml myapp

# GitLab Container Registry example
docker login registry.gitlab.com
docker tag myapp registry.gitlab.com/mygroup/myproject:latest
docker push registry.gitlab.com/mygroup/myproject:latest

8. Health Checks & High Availability

# Health check in Dockerfile
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=60s \
  CMD curl -f http://localhost/health || exit 1

# Health check in service create
docker service create \
  --health-cmd "curl -f http://localhost/health || exit 1" \
  --health-interval 30s \
  --health-timeout 10s \
  --health-retries 3 \
  --health-start-period 60s \
  --name web myapp

# Manager HA: Use 3 or 5 managers (odd number for Raft quorum)
# 3 managers → tolerates 1 failure
# 5 managers → tolerates 2 failures
# 7 managers → tolerates 3 failures (max recommended)

# Check manager status
docker node ls --filter role=manager

# Drain a manager for maintenance (stays manager but no tasks)
docker node update --availability drain manager-2

Quorum Table

ManagersQuorumTolerated Failures
110
220 (avoid!)
321
532
743

9. Monitoring & Logging

# Built-in monitoring
docker node ls                        # Node status
docker service ls                     # Service status
docker service ps <service>           # Task status per service
docker stats                          # Container resource usage (local)
docker system df                      # Disk usage

# Prometheus + Grafana stack for Swarm
# Enable Docker metrics endpoint in /etc/docker/daemon.json:
{
  "metrics-addr": "0.0.0.0:9323",
  "experimental": true
}

# Key metrics to monitor:
# - Node count and status
# - Service replica count (actual vs desired)
# - Container CPU/memory usage
# - Network I/O per service
# - Disk usage per node
# - Task failure rate

Centralized Logging

# Global logspout (one per node)
docker service create \
  --name logspout \
  --mode global \
  --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
  gliderlabs/logspout \
  syslog+tcp://logstash:5000

# Or use Docker logging drivers
docker service create \
  --log-driver gelf \
  --log-opt gelf-address=udp://logstash:12201 \
  --name web myapp

10. Troubleshooting & Best Practices

Common Issues

ProblemCauseFix
Service stuck at 0/N replicasImage pull failure, constraintsdocker service ps --no-trunc <svc>
Tasks in "Rejected" stateResource constraints too tightIncrease limits or add nodes
Network connectivity issuesOverlay network misconfiguredCheck firewall ports 2377, 7946, 4789
Manager quorum lostToo many managers downdocker swarm init --force-new-cluster
Slow deploymentsLarge images, slow registryUse smaller images, local registry
Service not reachablePort not publishedCheck docker service inspect for ports
Secrets not availableService not updatedRedeploy service with new secret

Required Firewall Ports

PortProtocolPurpose
2377TCPCluster management
7946TCP/UDPNode communication
4789UDPOverlay network (VXLAN)

Best Practices

  • ▸Use 3 or 5 managers (odd number, never just 1 in production)
  • ▸Keep managers as managers only (drain them from workloads)
  • ▸Always use --with-registry-auth for private registries
  • ▸Use placement constraints + labels for workload distribution
  • ▸Set resource limits on all services
  • ▸Use health checks on every service
  • ▸Use update_config.order: start-first for zero-downtime deploys
  • ▸Pin image versions (never use :latest in production stacks)
  • ▸Back up Swarm state: /var/lib/docker/swarm/
  • ▸Use overlay networks with internal: true for backend services
  • ▸Rotate join tokens periodically: docker swarm join-token --rotate worker