Perspective V Docs

Docker Swarm Migration

Migration from docker-compose to Docker Swarm mode on the Contabo VPS.

Overview

The VPS is being migrated from docker compose to Docker Swarm mode for native service orchestration, encrypted secrets management, rolling updates, and automatic service reconciliation.

Key principle: swarm/ is the active deployment system, runtime/ retains Compose rollback assets, and host-wide tooling lives in operations/.

Rationale

  • Service reconciliation: Swarm automatically restarts failed services without external tooling
  • Encrypted secrets: Docker Secrets are encrypted at rest and only decrypted in-memory for services
  • Rolling updates: docker stack deploy triggers zero-downtime rolling updates for stateless services
  • Native orchestration: No dependency on external orchestrators or custom watchdog scripts beyond Watchtower/WUD

Migration Phases

  1. Prepare — All swarm artifacts created in swarm/ (compose files, scripts, secrets, configs)
  2. Pre-flight — Full DB backup via tier1 scripts, volume snapshot
  3. Build WordPress images — Build and push 5 WordPress images to the Gitea registry (gitea.perspective-v.com/perspective-v/)
  4. Decommission compose — docker compose down all stacks, remove bridge networks
  5. Init Swarm — docker swarm init, create overlay networks
  6. Create secrets — ./swarm/secrets/create-all-secrets.sh
  7. Deploy stacks — Deploy database, platform, edge, service, panel, and websites-* stacks in dependency order
  8. Validate — HTTP checks, DB connectivity, NetBird access, ACME certs

What Changes

AspectBefore (compose)After (Swarm)
Deploy commanddocker compose --env-file ... up -d./swarm/scripts/<stack>.sh deploy
Secrets.env files on diskdocker secret (encrypted)
TraefikDocker socket providerSwarm API provider
Container namesredis, postgres, etc.platform_redis.<id>
Networksbridge driveroverlay driver (attachable)
Labelslabels:deploy.labels:

What Stays the Same

  • All .env files in runtime/environments/vps/ (shared between both modes)
  • CI/CD pipelines (same images work for both)
  • Systemd backup timer names and schedules; their repository executables are maintained under operations/
  • DNS records
  • UFW firewall rules
  • ACME certificates at /var/lib/traefik/letsencrypt/acme.json
  • RustFS data at /var/lib/rustfs/

Rollback Procedure

# Remove all Swarm stacks
docker stack ls --format '{{.Name}}' | xargs -n1 docker stack rm
# Leave Swarm
docker swarm leave --force
# Recreate bridge networks
docker network create proxy && docker network create platform && \
docker network create postgres-network && docker network create mssql-network && \
docker network create mysql-network && docker network create mongodb-network && \
docker network create object-storage
# Redeploy from compose
./runtime/scripts/infrastructure/edge-traefik.sh up
# ... all stacks in order

Volumes are preserved — no data loss on rollback.

WordPress Images

Five WordPress stacks use pre-built images. Build and push before Swarm deploy:

# token is a Gitea access token with write:package scope
echo "<TOKEN>" | docker login gitea.perspective-v.com -u <username> --password-stdin
docker build --build-arg APP_HOST=wp.dbskc.com -t gitea.perspective-v.com/perspective-v/dbskc-web:latest runtime/stacks/services/dbskc.com/wp.dbskc.com/
docker push gitea.perspective-v.com/perspective-v/dbskc-web:latest
# Repeat for nishatcolony-pk, gorsistudio-web, gorsistudio-store, wcblahore-pk

Dev Environment

A complete local development setup is supported using Docker Swarm. See swarm/README.md for the full guide. Quick reference:

# Init local Swarm
docker swarm init --advertise-addr 127.0.0.1

# Create overlay networks (same names as VPS)
docker network create --driver overlay --attachable proxy
docker network create --driver overlay --attachable platform
docker network create --driver overlay --attachable postgres-network
docker network create --driver overlay --attachable mssql-network
docker network create --driver overlay --attachable mysql-network
docker network create --driver overlay --attachable mongodb-network
docker network create --driver overlay --attachable object-storage

# Create secrets from dev env files
./swarm/secrets/create-dev-secrets.sh

# Deploy with 'dev' flag
./swarm/scripts/databases/database.sh deploy dev
./swarm/scripts/platform/platform.sh deploy dev
./swarm/scripts/edge/edge.sh deploy dev
./swarm/scripts/services/service.sh deploy dev
./swarm/scripts/services/kong.sh deploy dev
./swarm/scripts/services/netbird.sh deploy dev
./swarm/scripts/services/zitadel.sh deploy dev
./swarm/scripts/panels/panels.sh deploy dev
# ... continue in dependency order

Differences from VPS:

  • Uses runtime/environments/dev/**/*.dev.env (git-tracked templates with ChangeThis* placeholders)
  • VPS .env files are NOT used in dev — secrets come from dev env files via create-dev-secrets.sh
  • Launchers accept a dev argument: ./swarm/scripts/<stack>.sh deploy dev
  • Traefik uses self-signed certs (no Let's Encrypt on localhost)
  • Host bind mounts (letsencrypt, rustfs) are skipped — use Docker named volumes

DEV limitations:

  • NetBird requires real DNS — skip on pure local dev
  • WordPress stacks need images pushed to private registry — build locally or skip
  • SMTP-dependent services (Zitadel, Vaultwarden, Kener) — use Mailpit or skip
  • WUD custom registry auth — skip WUD or use mock registry

Administration UIs are isolated in the panel split stack and labeled com.perspective-v.role=panel. Gitea, RustFS, and Vaultwarden share service; Kong, NetBird, and Zitadel use service-kong, service-netbird, and service-zitadel. Kener remains in the split edge stack. Public/client-facing applications use websites-<site>.

State docs:

  • docs/state/next-steps.mdx — Swarm migration checklist
  • docs/state/requirements.mdx — Swarm migration requirements

On this page