August 19, 2026
Docker Compose to Docker Swarm: A Zero-Downtime Migration Guide (2026)
When to move from Docker Compose to Swarm, how to convert a Compose file into a stack, run both side by side, cut traffic over with no downtime, and the pitfalls around volumes, ports and secrets.

Still running your services with plain docker compose in 2026? You’re not alone. Many homelabbers and small teams are happily using Compose, but once your setup grows beyond a few services or a single machine, Docker Swarm offers significant advantages in high availability, easier updates, scaling, and operational simplicity. Check out our comparison between SwarmCLI, Portainer, and native Docker to see why it's the natural next step.
This comprehensive guide walks you through a safe, zero-downtime migration from Docker Compose to Docker Swarm.
Why Migrate from Compose to Swarm in 2026?
Key Benefits:
- Built-in load balancing and service discovery
- Automatic failover if a node fails
- Rolling updates with zero downtime
- Easy horizontal scaling across multiple machines
- Better secret and config management
- Single control plane for multi-node environments
Swarm gives you “just enough orchestration” without the complexity of Kubernetes.
When You Should (and Shouldn’t) Migrate
Migrate if:
- You have 5+ services
- You want multi-node redundancy
- You need zero-downtime deployments
- You’re tired of manually managing multiple machines
Stay on Compose if:
- Everything runs perfectly on a single powerful machine
- You have very simple needs
- You prefer maximum simplicity
Prerequisites
- Docker Engine 24 or newer; Swarm mode is part of the engine
- At least 2–3 nodes (you can start with a single-node Swarm)
- Backup of all your data and volumes
- Traefik or another reverse proxy (highly recommended)
- SwarmCLI, installed on your laptop or the manager node for rapid service management and health monitoring.
[!TIP] SwarmCLI tip: Before the final cutover, open the new stack in SwarmCLI. Every task's state, node and last error is on one screen, which is the view
docker service psmakes you assemble by hand.
Phase 1: Preparation (Do This First)
-
Standardize Your Compose Files
- Name the file
compose.yml, the current convention - Drop the top-level
version:key;docker stack deployignores it and warns - Add
deploy:keys now, so the file already describes replicas, updates and limits
- Name the file
-
Inventory Your Services
- Identify stateful vs stateless services
- Note which services need sticky sessions
- Document external dependencies (databases, storage)
Phase 2: The Safe Migration Strategy (Zero Downtime)
Step 1: Initialize Swarm
# On your primary node
docker swarm init --advertise-addr <your-ip>
# Join other nodes as workers or managers
docker swarm join --token <token> <manager-ip>:2377
Step 2: Convert Compose to Stack File
Most Compose files work with minimal changes. Here’s a before/after example:
Old compose.yml:
services:
web:
image: myapp:latest
ports:
- '8080:80'
New stack.yml:
services:
web:
image: myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
failure_action: rollback
rollback_config:
parallelism: 1
ports:
- '8080:80'
networks:
- frontend
Step 3: Deploy as Stack (Parallel Run)
This is the safest method:
# Deploy the new Swarm stack alongside your existing Compose
docker stack deploy -c stack.yml myapp
Both Compose and Swarm versions run in parallel. Gradually shift traffic using your reverse proxy (Traefik).
What changes in the file: the keys Swarm keeps, changes or ignores
docker stack deploy reads the Compose format but not every Compose key. It warns about the ones it ignores, and the warning scrolls past. This is the list to check before the first deploy:
| Compose key | Under docker stack deploy |
|---|---|
build | Ignored. Build and push the image first; the stack file names image: only |
container_name | Ignored. Tasks are named <stack>_<service>.<n> |
depends_on | Ignored. Services start in parallel; make the application retry its connections |
restart | Ignored. Use deploy.restart_policy (condition, delay, max_attempts) |
mem_limit, cpus (top-level) | Ignored. Use deploy.resources.limits and reservations |
ports (short syntax) | Kept, but published on every node through the routing mesh, not only where the task runs |
ports with mode: host | Kept; binds on the node that runs the task, so pair it with a placement constraint |
volumes (named) | Kept, but a named volume is local to the node the task lands on; pin stateful services |
volumes (bind mounts) | Kept; the path must exist on whichever node runs the task |
env_file, environment | Kept; move credentials to secrets: with the _FILE convention |
healthcheck, logging, user, hostname | Kept |
read_only, tmpfs, cap_add, cap_drop | Kept |
privileged, security_opt, devices | Ignored; there is no privileged service. GPUs go through generic resources |
network_mode, links | Ignored. Services find each other by name on an overlay network |
deploy.* | The whole point: replicas, update_config, rollback_config, placement, resources, restart_policy |
The one that bites hardest is restart: silently doing nothing; a service with no restart_policy still restarts on failure, but with Swarm's defaults rather than yours.
Moving the data
Compose volumes live on the machine Compose ran on. A Swarm named volume is created fresh on whichever node first runs the task, so the migration of a stateful service is a copy, not a rename:
- Label the node that will own the data:
docker node update --label-add db-primary=true <node>, and give the service a matchingplacement.constraints. - Stop the Compose service, so the data is quiescent.
- Copy the volume to that node. On the same machine,
docker run --rm -v old_volume:/from -v new_volume:/to alpine cp -a /from/. /to/does it; across machines,tarthe/fromdirectory throughssh. - Deploy the stack. The task lands on the labelled node and finds its data.
Do this once per stateful service and never for stateless ones, which need no volume at all. The HA database guide covers what to do with that data once it is in the swarm.
Phase 3: Zero-Downtime Cutover Techniques
With Traefik as the reverse proxy on both sides, the cutover is a label change. The Compose service has traefik.http.routers.shop.rule=Host(shop.example.com); the Swarm service declares the same router under deploy.labels (Swarm reads service labels from deploy.labels, not the service's top-level labels) with a higher priority, and Traefik routes new requests to the swarm service the moment its health check passes:
services:
web:
image: shop/web:2.1.0
deploy:
replicas: 2
labels:
- traefik.enable=true
- traefik.http.routers.shop-swarm.rule=Host(`shop.example.com`)
- traefik.http.routers.shop-swarm.priority=20
- traefik.http.services.shop-swarm.loadbalancer.server.port=8080
- traefik.http.services.shop-swarm.loadbalancer.healthcheck.path=/healthz
networks: [traefik-public]
Watch the error rate for a few minutes, then remove the Compose service. If something is wrong, lower the priority and the old service takes the traffic back.
Best Approach – Blue/Green Style:
- Deploy new Swarm stack with new service names (
myapp-v2) - Point Traefik to the new stack
- Monitor for issues
- Shut down the old Compose services once stable
Rolling Migration (for simpler apps):
docker stack deploy -c stack.yml myapp
# Then slowly scale down old Compose containers
Phase 4: Post-Migration Optimizations
Add These Deploy Features:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
placement:
constraints:
- node.role == worker
restart_policy:
condition: on-failure
Networking Best Practices:
- Use overlay networks
- Create dedicated networks (
frontend,backend,database)
Common Migration Pitfalls & Solutions
| Pitfall | Solution |
|---|---|
| Named volumes are node-local | A volume created on one node does not follow the task to another; pin stateful services with constraints (see the HA database guide) |
ports: behaves differently | Swarm publishes through the routing mesh on every node by default; use mode: host when a service must bind on the node it runs on |
| Port conflicts | Remove old Compose ports before final cutover |
| Database connection issues | Use service name as hostname (Swarm DNS) |
| Slow deployments | Tune update_config (parallelism, delay, order) |
| Lost secrets | Convert environment variables to Swarm secrets |
Advanced: Making Migration Even Smoother
- Use Portainer to visually manage both old and new stacks during transition
- SwarmCLI: restart a single service from its view if it misbehaves after moving to the overlay network, or run
docker service update --force <service>for the same effect. - Implement health checks in all services
- Add Prometheus monitoring before migration
- Create a rollback plan (keep old Compose files ready)
Conclusion: Swarm is the Natural Next Step
Moving from Docker Compose to Docker Swarm in 2026 is easier than ever. With careful planning and the parallel stack approach, you can achieve a smooth, zero-downtime migration that unlocks high availability and better operational experience. Whether you're building an Ultimate Homelab Stack or a production platform, Swarm is the right choice.
Swarm gives you enterprise-grade features while staying true to the simplicity that made Docker popular. Combined with SwarmCLI, the management of your new Swarm cluster will feel just as familiar and fast as your old Compose workflow.
Your Migration Action Plan:
- Start with a single-node Swarm test
- Convert and deploy one non-critical service first
- Gradually move the rest
- Celebrate when you run
docker service lsinstead of managing multiple compose files!
If you have read that Swarm is a dead end, it is not: Docker Swarm is still maintained and supported in 2026. And once the stack is up, the Definitive Docker Swarm Guide covers the HA, networking and secrets work that comes next.