devops2025-02-15Β·9Β·199/348

Docker Swarm Cluster Setup: Overcoming Overlay Network and Service Discovery Issues

Setting up a Docker Swarm cluster from scratch and resolving common issues with overlay networks, service discovery, and inter-service communication.

Introduction

Docker Swarm does not get as much attention as Kubernetes, but for small to medium deployments, it is incredibly practical. I have been running a three-node Swarm cluster for about a year to host a set of microservices that power a personal project β€” a quantitative trading backtesting platform. The cluster handles scheduling, service discovery, and rolling updates with minimal configuration overhead.

However, getting Swarm working correctly took more effort than I expected. The initial cluster formation was easy, but overlay networking and inter-service communication had several gotchas that are not well documented. This post walks through the complete setup process and the issues I encountered along the way.

Environment

  • Nodes: 3x Hetzner Cloud CX22 (Ubuntu 22.04)
  • Docker version: 24.0.7
  • Manager nodes: 1 (for simplicity)
  • Worker nodes: 2
  • Network: Private network 10.0.0.0/24 between nodes
  • Services: Go API server, Redis, PostgreSQL, Prometheus, Grafana

I chose a single-manager architecture because the cluster is small and does not need high availability for the control plane. If the manager goes down, I can promote a worker manually.

Problem

After initializing the Swarm and joining the worker nodes, I created an overlay network and deployed services. The services started successfully, but inter-service communication failed. Specifically:

  1. The Go API server could not connect to Redis using the service name redis
  2. DNS resolution within containers returned NXDOMAIN for service names
  3. Containers on different nodes could not communicate over the overlay network

The first issue suggested a DNS problem. I verified that the Docker DNS resolver was configured inside the containers:

docker exec -it go-api cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0

The DNS resolver was configured correctly. But when I tried to resolve the service name:

docker exec -it go-api nslookup redis
# ;; connection timed out; no servers could be reached

DNS resolution was timing out, which pointed to an issue with the overlay network itself.

Analysis

I investigated the overlay network configuration:

docker network ls
# NETWORK ID     NAME              DRIVER    SCOPE
# abc123def456   ingress           overlay   swarm
# 789xyz012abc   my-overlay        overlay   swarm

docker network inspect my-overlay
# [
#   {
#     "Name": "my-overlay",
#     "Driver": "overlay",
#     "Scope": "swarm",
#     "IPAM": {
#       "Driver": "default",
#       "Config": [
#         {
#           "Subnet": "10.0.9.0/24",
#           "Gateway": "10.0.9.1"
#         }
#       ]
#     },
#     "Options": {
#       "com.docker.network.driver.overlay.vxlanidlist": "4097"
#     }
#   }
# ]

The network was created but the VXLAN tunnel endpoints were not connecting properly between nodes. I checked the Docker daemon logs on the worker nodes:

journalctl -u docker.service --since "1 hour ago" | grep -i overlay
# level=error msg="failed to add the host (vxlan) interface: operation not supported"

This error indicated that the VXLAN kernel module was not loaded. Docker Swarm overlay networks use VXLAN for encapsulation, and the kernel module must be available. I checked:

lsmod | grep vxlan
# (empty β€” the module was not loaded)

sudo modprobe vxlan
lsmod | grep vxlan
# vxlan                   12345  0

After loading the VXLAN module, the overlay network started working on that node. But I also discovered another issue β€” the firewall ports required for Swarm communication were not open.

Docker Swarm requires several ports to be open between nodes:

  • TCP 2377: Cluster management
  • TCP/UDP 7946: Inter-node communication
  • UDP 4789: VXLAN overlay network traffic

I had opened the TCP ports but missed UDP 4789, which is essential for the overlay network.

Solution

I created a setup script that handles all the prerequisites for joining a Swarm cluster:

#!/bin/bash
# setup-swarm-node.sh β€” Run on each worker node before joining

set -euo pipefail

echo "Loading required kernel modules..."
sudo modprobe vxlan
sudo modprobe ip_vs
sudo modprobe ip_vs_rr
sudo modprobe ip_vs_wrr
sudo modprobe ip_vs_sh
sudo modprobe nf_conntrack

# Ensure modules load on boot
cat <

For the manager node, I initialized the Swarm:

# On the manager node
docker swarm init --advertise-addr 10.0.0.1

# Get the join token
docker swarm join-token worker
# Output: docker swarm join --token SWMTKN-1-xxxxx 10.0.0.1:2377

On each worker node, I ran the setup script and then the join command:

sudo bash setup-swarm-node.sh
docker swarm join --token SWMTKN-1-xxxxx 10.0.0.1:2377

After all nodes joined, I created the overlay network with the correct settings:

docker network create \
  --driver overlay \
  --subnet 10.0.9.0/24 \
  --opt encrypted \
  my-overlay

The --opt encrypted flag enables encryption for inter-node traffic, which is important for production environments.

I verified the cluster was healthy:

docker node ls
# ID                            HOSTNAME    STATUS    AVAILABILITY   MANAGER STATUS
# abc123 *                      manager     Ready     Active         Leader
# def456                        worker1     Ready     Active
# ghi789                        worker2     Ready     Active

And tested DNS resolution within a service:

docker service create --name test-dns --network my-overlay alpine sleep 3600
docker exec -it $(docker ps -q -f name=test-dns) nslookup redis
# Name:      redis
# Address:   10.0.9.5

DNS resolution was working correctly across the overlay network.

For deploying services, I created a stack file:

# docker-stack.yml
version: '3.8'

services:
  api:
    image: myregistry/go-api:latest
    networks:
      - backend
    deploy:
      replicas: 2
      restart_policy:
        condition: on-failure
    ports:
      - "8080:8080"

  redis:
    image: redis:7-alpine
    networks:
      - backend
    volumes:
      - redis-data:/data

  postgres:
    image: postgres:16-alpine
    networks:
      - backend
    volumes:
      - pg-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: trading
      POSTGRES_PASSWORD_FILE: /run/secrets/db-password
    secrets:
      - db-password

networks:
  backend:
    driver: overlay

volumes:
  redis-data:
  pg-data:

secrets:
  db-password:
    file: ./secrets/db-password.txt

Deployed with:

docker stack deploy -c docker-stack.yml trading

Lessons Learned

  1. Load kernel modules before joining β€” VXLAN is required for overlay networks. If the module is not loaded, the network will appear to create successfully but inter-node communication will fail silently.
  2. Open UDP 4789 β€” Most firewall guides focus on TCP ports, but VXLAN uses UDP 4789 for overlay traffic. Missing this port is the most common cause of overlay network failures.
  3. Use a dedicated private network β€” Swarm overlay traffic should go over a private network, not the public internet. This improves performance and security.
  4. Enable encryption for production β€” The --opt encrypted flag adds overhead but protects inter-node traffic. Use it for anything handling sensitive data.
  5. Test DNS resolution early β€” After setting up the cluster, verify that services can resolve each other by name before deploying your actual application.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.