Running a single container on a single host is easy. Running dozens of containers across multiple machines, keeping them healthy, load-balanced, and scalable — that’s a different problem entirely. Docker Swarm is Docker’s native answer to that problem, and in this guide I’ll walk through everything from the underlying concepts to a real multi-node deployment you can build and scale yourself.
What Docker Swarm Actually Is
Docker Swarm is a container orchestration tool built directly into the Docker Engine. Unlike Kubernetes, which is a separate system you install on top of a container runtime, Swarm mode ships with every Docker installation and can be turned on with a single command. It turns a group of Docker hosts into a single virtual host, letting you deploy services declaratively and have Swarm figure out where each container (in Swarm terminology, a “task”) should run.
Swarm’s core building blocks are:
- Nodes — a physical or virtual machine running Docker Engine in swarm mode. Nodes are either managers (which handle orchestration and cluster state) or workers (which just run tasks).
- Services — a declarative definition of a container you want to run across the swarm, including image, replicas, ports, and networks.
- Tasks — a single running container that belongs to a service. If a task dies, Swarm creates a replacement automatically.
- Overlay networks — a virtual network spanning multiple hosts, letting containers on different physical machines talk to each other as if they were on the same LAN.
Prerequisites
You’ll need at least two Linux machines (three or more is recommended for production so manager quorum survives a single failure) with Docker Engine installed, and open network ports between them:
- TCP 2377 — cluster management communication
- TCP/UDP 7946 — node-to-node communication
- UDP 4789 — overlay network traffic (VXLAN)
Verify Docker is installed and running on each host:
docker --version
docker info
Step 1: Initialize the Swarm
On what will become your first manager node, run:
docker swarm init --advertise-addr 192.168.1.10
Replace 192.168.1.10 with the actual IP address of this host that other nodes can reach. The output looks like this:
Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c 192.168.1.10:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Docker generates a join token — treat it like a credential, since anyone who has it can join your cluster.
Step 2: Join Worker Nodes
On each additional machine, run the exact docker swarm join command printed in the previous step:
docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c 192.168.1.10:2377
Expected output:
This node joined a swarm as a worker.
If you lost the token, retrieve it again from the manager:
docker swarm join-token worker
docker swarm join-token manager
Step 3: Verify Cluster State
Back on the manager, confirm all nodes have joined:
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
dxn1zf6l61qsb1josjja83ngz * manager1 Ready Active Leader
2p5s5s5s5s5s5s5s5s5s5s5s5s worker1 Ready Active
9m9m9m9m9m9m9m9m9m9m9m9m9m worker2 Ready Active
The asterisk marks the node you’re currently connected to.
Step 4: Create an Overlay Network
Before deploying a service, create an overlay network so containers across hosts can reach each other by service name:
docker network create --driver overlay --attachable my-overlay-net
Step 5: Deploy a Service
Now deploy a containerized application as a Swarm service:
docker service create \
--name web \
--replicas 3 \
--network my-overlay-net \
--publish published=8080,target=80 \
nginx:latest
This tells Swarm: run three replicas of nginx:latest, attach them to my-overlay-net, and expose port 80 inside the container as port 8080 on every node in the cluster (this is Swarm’s routing mesh — you can hit port 8080 on any node, even ones not running the container, and get routed to a healthy replica).
Check the service status:
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
qs8p9wjyzhv6 web replicated 3/3 nginx:latest *:8080->80/tcp
Inspect where each task landed:
docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
xv2k9jz6qh1t web.1 nginx:latest manager1 Running Running 2 minutes ago
p3n8s7wj2k4m web.2 nginx:latest worker1 Running Running 2 minutes ago
q9x1m4t8vn3r web.3 nginx:latest worker2 Running Running 2 minutes ago
Scaling Up and Down
Scaling is a single command — this is where Swarm shines for simplicity:
docker service scale web=6
web scaled to 6
overall progress: 6 out of 6 tasks
1/6: running
2/6: running
...
verify: Service converged
Swarm automatically schedules the new replicas across available nodes, respecting resource constraints and placement rules you define.
Using Compose Files for Stacks
For anything beyond a trivial demo, you’ll want to define your services declaratively in a Compose file and deploy it as a stack:
# docker-stack.yml
version: "3.9"
services:
web:
image: nginx:latest
deploy:
replicas: 3
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.5"
memory: 256M
placement:
constraints:
- node.role == worker
ports:
- "8080:80"
networks:
- my-overlay-net
api:
image: myregistry/api-app:1.4
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
networks:
- my-overlay-net
networks:
my-overlay-net:
driver: overlay
Deploy it with:
docker stack deploy -c docker-stack.yml myapp
Creating network myapp_my-overlay-net
Creating service myapp_web
Creating service myapp_api
List running stacks and their services:
docker stack ls
docker stack services myapp
Internal Architecture: How Swarm Actually Schedules Work
Under the hood, Swarm managers run a Raft consensus algorithm to keep cluster state consistent. This is why an odd number of managers (1, 3, 5) is recommended — Raft needs a majority (quorum) to agree on state changes, and an odd count avoids tie situations during network partitions. With 3 managers, the cluster can tolerate 1 manager failure; with 5, it tolerates 2.
The scheduler uses a spread strategy by default, placing tasks on the node with the fewest running tasks to balance load. You can override this with placement constraints (node.labels, node.role, engine.labels) or preferences (spread=node.labels.zone).
Networking relies on VXLAN encapsulation for overlay networks — each packet between containers on different hosts is wrapped in a VXLAN header and sent over UDP 4789, then unwrapped on the receiving host. This is why that port must be open between all nodes.
The routing mesh works via IPVS (IP Virtual Server) load balancing at the kernel level combined with an internal DNS-based service discovery — every service gets a virtual IP (VIP), and requests to the published port on any node get load-balanced across all healthy task IPs behind that VIP.
Rolling Updates and Rollbacks
Swarm supports zero-downtime rolling updates natively:
docker service update --image myregistry/api-app:1.5 --update-parallelism 1 --update-delay 20s myapp_api
If the new version misbehaves, roll back instantly:
docker service rollback myapp_api
Security Considerations
- Swarm encrypts all manager-to-manager and manager-to-worker control-plane traffic with mutual TLS automatically — no extra configuration is required for this baseline layer.
- Enable overlay network payload encryption for sensitive data-plane traffic:
docker network create --opt encrypted --driver overlay secure-net. - Use
docker secretto inject credentials into containers rather than environment variables or baked-in config files:
echo "supersecretpassword" | docker secret create db_password -
services:
db:
image: postgres:16
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
external: true
- Rotate join tokens periodically with
docker swarm join-token --rotate worker. - Lock the swarm to require a manual unlock key on manager restart:
docker swarm update --autolock=true.
Monitoring and Troubleshooting
Check service logs across all replicas:
docker service logs -f myapp_web
Inspect a specific task’s health:
docker inspect --format '{{.Status.State}}' <task-id>
Common issues and fixes:
| Symptom | Likely Cause | Fix |
|---|---|---|
Service stuck at 0/3 replicas | Image not accessible from worker nodes | Push image to a registry reachable by all nodes |
| Tasks constantly restarting | Health check failing or app crashing | Check docker service logs, verify health check command |
Nodes show Down | Firewall blocking 2377/7946/4789 | Open required ports between hosts |
| Manager quorum lost | Too many managers offline simultaneously | Restore from an odd-numbered majority of managers |
For ongoing monitoring, pair Swarm with Prometheus using the cadvisor exporter alongside node-exporter, or use Docker’s own docker stats for a quick live view:
docker stats
Real-World Deployment Pattern
A typical production layout: 3 dedicated manager nodes (small VMs, no application workloads scheduled on them via --availability drain), and N worker nodes sized for your workload. Managers are drained from scheduling to protect cluster stability:
docker node update --availability drain manager1
Application services are deployed as stacks via CI/CD pipelines, with image tags pinned to specific versions (never latest in production), and secrets and configs managed through docker secret and docker config.
Summary
Docker Swarm gives you clustering, service discovery, load balancing, rolling updates, and secret management with tools already built into the Docker CLI — no separate control plane to install. It’s an excellent choice when you want orchestration without the operational overhead of Kubernetes, especially for small-to-mid-sized deployments. You’ve now seen how to initialize a cluster, join nodes, deploy and scale services, define stacks declaratively, and secure and monitor the whole thing.
References
- Docker Swarm mode overview: https://docs.docker.com/engine/swarm/
- Docker Swarm networking: https://docs.docker.com/engine/swarm/networking/
- Docker Compose file reference (deploy key): https://docs.docker.com/compose/compose-file/deploy/
- Docker secrets: https://docs.docker.com/engine/swarm/secrets/
- CNCF landscape (for comparing orchestrators): https://landscape.cncf.io/