Set Up Docker Swarm Mode for Multi-Node Container Orchestration on Ubuntu

Written by: Bagus Facsi Aginsa
Published at: 04 Aug 2026


You have containers running on one server with Docker Compose, and it works fine until that server runs out of capacity, or you need your application to survive a single machine going down. At that point you need more than one host running your containers, plus something that decides which host runs which container, restarts a container if it crashes, and lets you scale a service up or down without manually SSHing into three different machines.

That is what container orchestration solves, and Docker Swarm is the orchestrator that ships inside Docker itself. If you already know Docker and Docker Compose, as covered in the Docker and Docker Compose guide, Swarm mode is a natural next step: it reuses the same CLI, the same image format, and a syntax close enough to Compose files that your existing knowledge carries over directly.

This tutorial is for developers and sysadmins who are comfortable running containers on a single Ubuntu server with Docker and Docker Compose, but have not yet run a multi-node cluster. By the end, you will have a three-node Swarm cluster on Ubuntu, a stack deployed across it with a load-balanced service, and the practical commands to scale, update, and troubleshoot it.

Conceptual Overview

Docker Swarm turns a group of Docker hosts into a single logical cluster. Instead of running docker run on a specific machine, you submit your desired state (run 3 replicas of this image) to the cluster, and Swarm decides which nodes actually run those containers.

A few terms come up constantly:

Node. Any machine running Docker that has joined the swarm. A node is either a manager, which participates in cluster decisions and scheduling, or a worker, which only runs containers assigned to it.

Manager quorum. Managers use the Raft consensus algorithm to agree on cluster state. You should always run an odd number of managers (1, 3, or 5) so the cluster can tolerate a manager failing while still having a majority left to make decisions. A single manager works for testing but has no fault tolerance: if it goes down, you lose control of the cluster.

Service. The Swarm equivalent of “run this image with N replicas.” You declare a service once, and Swarm keeps that many containers (called tasks in Swarm terminology) running across the cluster, restarting them if they die.

Stack. A group of services defined together in a Compose-format YAML file and deployed as one unit with docker stack deploy. If you have written a docker-compose.yml before, a stack file looks almost identical.

Overlay network. A virtual network that spans every node in the swarm, so containers on different physical machines can reach each other by service name, exactly like containers on the same host reach each other in Compose.

Routing mesh. When you publish a port on a service, every node in the swarm listens on that port, and Swarm routes incoming traffic to a healthy container running that service, wherever it happens to live. You can hit any node’s IP on the published port and reach the service.

Prerequisites

To follow along you need:

  • Three Ubuntu 22.04 servers (or newer), each with at least 1 vCPU and 1 GB RAM. This tutorial uses 10.20.0.11 (manager), 10.20.0.12 (worker), and 10.20.0.13 (worker).
  • Docker Engine installed on all three nodes. If you have not installed Docker yet, follow the installation steps in the Docker and Docker Compose guide first, then come back here.
  • sudo access on all three servers.
  • Network connectivity between all three nodes on TCP ports 2377 (cluster management), 7946 (node communication), and UDP port 4789 (overlay network traffic).
  • Basic familiarity with Docker Compose syntax.

Step-by-Step Hands-On Tutorial

Step 1: Open the required firewall ports

If you are running UFW on each node, open the ports Swarm needs before initializing the cluster, since a blocked port here is the most common cause of nodes failing to join later.

sudo ufw allow 2377/tcp
sudo ufw allow 7946/tcp
sudo ufw allow 7946/udp
sudo ufw allow 4789/udp
sudo ufw reload

Run this on all three nodes. Port 2377 is only needed on managers, but opening it everywhere avoids problems if a worker is promoted to manager later.

Step 2: Initialize the swarm on the manager node

On 10.20.0.11, initialize the swarm and tell Docker which IP address other nodes should use to reach this manager:

sudo docker swarm init --advertise-addr 10.20.0.11

You will see output similar to this:

Swarm initialized: current node (x7z9k3m2p1qw) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-abc123...xyz789 10.20.0.11:2377

To add a manager to this swarm, run 'docker swarm join-token manager'.

Keep that docker swarm join command handy; you will run it on the worker nodes in the next step. The --advertise-addr flag matters on machines with more than one network interface, since it tells Swarm which IP other nodes should use to reach this manager, avoiding a private cloud metadata IP or a Docker bridge IP being advertised by mistake.

Step 3: Join the worker nodes

On 10.20.0.12 and 10.20.0.13, run the join command from the previous step’s output:

sudo docker swarm join --token SWMTKN-1-abc123...xyz789 10.20.0.11:2377

Each node should respond with:

This node joined a swarm as a worker.

If you lost the join token, regenerate it from the manager at any time:

sudo docker swarm join-token worker

Step 4: Verify the cluster

Back on the manager, list the nodes to confirm all three joined successfully:

sudo docker node ls
ID                            HOSTNAME     STATUS    AVAILABILITY   MANAGER STATUS
x7z9k3m2p1qw *                node-a       Ready     Active         Leader
b4n8v2c6d0fh                  node-b       Ready     Active
k1m5p9q3r7st                  node-c       Ready     Active

The asterisk marks the node you ran the command from. Only node-a shows a manager status here, since the other two joined as workers and can run containers but cannot make scheduling decisions.

Step 5: Deploy your first service

Deploy a simple Nginx service with three replicas, spread automatically across the cluster:

sudo docker service create --name web --replicas 3 --publish 8080:80 nginx:1.27

Check where the replicas landed:

sudo docker service ps web
ID             NAME      IMAGE         NODE      DESIRED STATE   CURRENT STATE
p3q7r1s5t9uv   web.1     nginx:1.27    node-a    Running         Running 20 seconds ago
m2n6o0p4q8rs   web.2     nginx:1.27    node-b    Running         Running 20 seconds ago
h5i9j3k7l1mn   web.3     nginx:1.27    node-c    Running         Running 20 seconds ago

Now request the service from any of the three node IPs, not just the one running a given replica:

curl -I http://10.20.0.12:8080
curl -I http://10.20.0.13:8080

Both return a valid HTTP response even though neither node is guaranteed to be running the container that answers the request. This is the routing mesh: it forwards the request internally to whichever node has a healthy replica.

Step 6: Deploy a multi-service stack

Real applications are usually more than one service. Create a stack file that mirrors what you would write in a Compose file, with a Swarm-specific deploy section added:

mkdir -p ~/blog-stack && cd ~/blog-stack
nano docker-compose.yml
version: "3.8"

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    networks:
      - app-net
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure

  api:
    image: node:20-alpine
    command: node -e "require('http').createServer((_,r)=>r.end('api ok')).listen(3000)"
    networks:
      - app-net
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure

networks:
  app-net:
    driver: overlay

The driver: overlay line is the important difference from a regular Compose file: it tells Swarm to create a network that spans every node, so the web service can reach api by name (http://api:3000) regardless of which nodes each container lands on.

Deploy the stack:

sudo docker stack deploy -c docker-compose.yml blog

List what got created:

sudo docker stack services blog
ID             NAME         MODE         REPLICAS   IMAGE            PORTS
a1b2c3d4e5f6   blog_web     replicated   2/2        nginx:1.27       *:8080->80/tcp
g7h8i9j0k1l2   blog_api     replicated   2/2        node:20-alpine

Step 7: Scale a service

Bumping capacity for a service under load takes one command, no manual container placement required:

sudo docker service scale blog_web=4

Swarm schedules two more replicas on whichever nodes have spare capacity, respecting your restart policy.

Step 8: Perform a rolling update

Update the web service to a newer Nginx image without dropping traffic:

sudo docker service update --image nginx:1.27.1 --update-parallelism 1 --update-delay 10s blog_web

--update-parallelism 1 replaces one replica at a time, and --update-delay 10s waits ten seconds between each replacement, giving the new container time to pass its health check before Swarm moves on to the next one. If something goes wrong mid-rollout, roll back with:

sudo docker service rollback blog_web

Common Mistakes & Troubleshooting

Nodes stuck in “Down” status. Usually a firewall problem. Double check that ports 2377/tcp, 7946/tcp, 7946/udp, and 4789/udp are open between all nodes, not just from the manager outward. Swarm’s gossip protocol is bidirectional.

“This node is not a swarm manager” error. You ran a manager-only command (like docker service create) on a worker node. Run it from the manager, or promote the worker first with docker node promote <NODE-ID> if it genuinely needs manager privileges.

Service stuck at 0/3 replicas. Check the task list for the actual error instead of guessing:

sudo docker service ps --no-trunc blog_web

The most common causes are an image that does not exist on a private registry the nodes cannot authenticate to, or a resource reservation in the deploy section that no node can satisfy.

Losing the swarm after a manager reboot. With only one manager, a reboot temporarily takes the whole cluster’s control plane down, even though worker nodes keep running their existing containers. This is exactly why production clusters should run three or five managers, never one.

Overlay network not resolving service names. This almost always means the network was created with the default bridge driver instead of overlay. Double check the driver: overlay line in your stack file; a network created without it will not span nodes.

Best Practices

  • Run an odd number of managers. Three managers tolerate one failure, five tolerate two. Never run two managers: that configuration has worse fault tolerance than a single manager, since losing either one breaks the quorum.
  • Keep managers small and dedicated where possible. On larger clusters, avoid scheduling regular application workloads onto manager nodes with docker node update --availability drain <NODE-ID>, so a runaway container cannot starve the control plane of resources.
  • Pin image tags, never rely on latest. A rolling update against latest can pull a different image on each node during the rollout, leaving you with a mixed-version deployment that is hard to diagnose.
  • Set resource limits in your stack files. Add resources.limits under deploy for memory and CPU so one runaway service cannot take down its neighbors on the same node.
  • Back up the swarm’s Raft state regularly. It lives under /var/lib/docker/swarm on manager nodes. Losing all managers without a backup means rebuilding the cluster from scratch.
  • Use secrets for credentials, not environment variables. docker secret create stores sensitive values encrypted in the Raft log and mounts them as files inside containers, which keeps them out of docker inspect output and shell history.

Conclusion

You now have a working three-node Docker Swarm cluster: a manager coordinating scheduling, two workers running containers, a stack deployed with an overlay network connecting its services, and the commands to scale and update that stack without downtime. The mental model carries over directly from Docker Compose, which is what makes Swarm a comfortable step up once a single host is not enough.

From here, a natural next step is looking at Kubernetes for larger or more complex deployments, since the concepts you just learned (services, replicas, rolling updates, overlay networking) map closely to Kubernetes equivalents (Deployments, Pods, and its own network model), just with more moving parts and more configuration flexibility. If your workloads stay in the range of a handful of services across a handful of nodes, though, Swarm’s simplicity is often the more practical choice.