Spin up a 1× manager + 2× worker Docker‑in‑Docker (DinD) Swarm locally, expose the manager’s Docker API on localhost:22375, and interact with it via a Docker context.
⚠️ Security note: this setup binds the Docker API without TLS for local testing only. Do not expose it on an untrusted network.
- Docker Engine (with the docker compose plugin)
- Linux/macOS/WSL recommended
- Ports 22375/tcp on localhost must be free
# From the repo folder that contains docker-compose.yml
docker compose up --buildYou may get errors like
worker2-1 | WARNING: ca-cert-placeholder.pem does not contain exactly one certificate or CRL: skipping
worker1-1 | WARNING: ca-cert-corporate-proxy.pem does not contain exactly one certificate or CRL: skipping
manager-1 | WARNING: ca-cert-corporate-proxy.pem does not contain exactly one certificate or CRL: skipping
worker2-1 | WARNING: ca-cert-corporate-proxy.pem does not contain exactly one certificate or CRL: skipping
worker2-1 | time="2025-12-12T08:56:10.038970524Z" level=info msg="Starting up"
worker1-1 | time="2025-12-12T08:56:10.038970875Z" level=info msg="Starting up"
manager-1 | time="2025-12-12T08:56:10.039031366Z" level=info msg="Starting up"
worker2-1 | failed to start daemon, ensure docker is not running or delete /var/run/docker.pid: process with PID 1 is still running
manager-1 | failed to start daemon, ensure docker is not running or delete /var/run/docker.pid: process with PID 1 is still running
worker1-1 | failed to start daemon, ensure docker is not running or delete /var/run/docker.pid: process with PID 1 is still running
worker2-1 exited with code 1
worker1-1 exited with code 1
manager-1 exited with code 1
dependency failed to start: container test-setup-manager-1 exited (1)
Then run
docker compose down -v
docker compose up --build --remove-orphansWait ~10–20 seconds for the Swarm to bootstrap (manager + workers join).
Check containers:
docker compose ps manager worker1 worker2Create a Docker context pointing to the manager API on 22375:
-
If you're using devcontainer:
docker context create swarmcli --description "Test SwarmCLI" --docker "host=tcp://host.docker.internal:22375"
-
Otherwise:
docker context create swarmcli --description "Test SwarmCLI" --docker "host=tcp://localhost:22375"
Use the new context:
docker --context swarmcli info | sed -n '/Swarm:/,/ClusterID/p'
docker --context swarmcli node lsYou should see Swarm: active and three nodes (1 manager, 2 workers).
Container names get a project prefix (e.g. test-manager-1). Always resolve the container dynamically:
# Exec into the manager container in a robust way
docker exec -it $(docker compose ps -q manager) sh -lc 'docker info | sed -n "/Swarm:/,/ClusterID/p"'
docker exec -it $(docker compose ps -q manager) docker node lsAlternatively, use the compose-native form (avoids dealing with names/IDs):
docker compose exec manager docker info | sed -n '/Swarm:/,/ClusterID/p'
docker compose exec manager docker node lsNote: The earlier example that used
docker exec -it test-manager-1 ...can fail if your Compose project name is nottest. Use the dynamic form above instead.
docker --context swarmcli service create --name whoami --publish 8080:80 traefik/whoami:v1.10
curl -fsS http://localhost:8080
docker --context swarmcli service rm whoamidocker --context swarmcli stack deploy -c test-setup/test-stack.yml demo# Stop and remove containers, networks, and volumes created by the stack
docker compose down -v💡 Tip: If you encounter "process with PID 1 is still running" errors when starting, clean up completely and rebuild:
docker compose down -v # Remove containers and volumes docker compose up --build
Very rarely the DinD dockerd inside the manager can linger. If ss shows the port still open:
sudo ss -ltnp | grep :22375 || sudo lsof -iTCP:22375 -sTCP:LISTEN -P -nFix by restarting your host Docker service (this won’t affect your system beyond restarting Docker):
# Linux (systemd)
sudo systemctl restart docker
# macOS / Docker Desktop: quit and relaunch Docker Desktop
# Windows / Docker Desktop: quit and relaunch Docker DesktopAfter restart, re‑run:
docker compose up -d-
To force a clean project prefix for container names, run:
docker compose --project-name test-setup up -d
Then your containers will be
test-manager-1,test-worker1-1, etc. -
To point the CLI directly without creating a context (temporary):
export DOCKER_HOST=tcp://localhost:22375 docker info
- manager: DinD with Docker API published on 22375/tcp (localhost)
- worker1, worker2: DinD workers joining the Swarm
- swarm-init: one-shot helper to initialize the manager and generate a join token
- worker-join*: one-shot helpers to join the workers
You’ll see deprecation warnings about binding the Docker API without TLS. This is expected for this local testbed. For any real environment, enable TLS with client verification.