Skip to content
SHC Docs

shc move

The shc move command relocates stack deployments between nodes. This enables node cycling, infrastructure migrations, and maintenance scenarios.

Move is designed for relocating stacks to different infrastructure, not for load balancing (use scaling and orchestrator-level scheduling for that).

Key Insight: Move = Backup + Uninstall + Recover. This reuses the proven backup infrastructure for data consistency and reliability.

OperationFlowOriginal Stack
MoveBackup -> Uninstall -> RecoverDeleted first
CloneBackup -> RecoverKept

Rename or relocate a stack. Both the source and the target stack name are required positional arguments (shc move <source> <target>); pass the same name twice to keep the name and only change the node/environment/tenant.

Terminal window
# Rename a stack (same node)
shc move postgres postgres-new
# Move a stack to a specific node (keep its name)
shc move postgres postgres --node node-abc123
# Move with stack rename
shc move postgres postgres-new --node node-abc123
# Move entire integration graph (all connected stacks)
shc move nextcloud nextcloud --node node-abc123 --all-connections
# Move to a different environment
shc move postgres postgres --target-environment staging
# Cross-tenant move (requires system config permission)
shc move postgres postgres --target-tenant other-org
# Dry run (show what would happen)
shc move postgres postgres --node node-abc123 --dry-run
ArgumentRequiredDescription
<source>YesStack to move (positional)
<target>YesNew stack name (same as source to keep it)
FlagDefaultDescription
--node, -nTarget node (optional, or swarm for Swarm)
--target-environmentsame as sourceTarget environment
--target-tenantsame as sourceTarget tenant (requires system config permission)
--all-connections, -afalseMove entire integration graph
--rename, -rRename dependencies: old:new,old2:new2
--dry-runfalseShow what would happen without executing
--yes, -yfalseSkip the confirmation prompt
--presumed-deadfalseMove off a DEAD node reusing surviving volumes
--discard-localfalseWith --presumed-dead: abandon local-only mounts
Terminal window
# Move database to specific node
shc move postgres postgres --node node-abc123
# Output:
# Moving postgres -> node-abc123
# v Backing up postgres
# v Uninstalling postgres from node-xyz789
# v Recovering postgres on node-abc123
# Move complete

Move a stack to run as Swarm services (spans all Swarm nodes):

Terminal window
# Move to Swarm orchestration
shc move webapp webapp --node swarm
# The stack now runs as Swarm services with overlay networking

Pass a different target name (the second positional) to rename during the move:

Terminal window
# Move and rename in one operation
shc move postgres db-primary --node node-abc123
# Equivalent to:
# 1. Backup postgres
# 2. Uninstall postgres
# 3. Recover as db-primary on node-abc123

To rename dependencies of a connected move, use --rename with comma-separated old:new pairs:

Terminal window
shc move nextcloud nextcloud --node node-abc123 --all-connections \
--rename postgres:postgres-2,keycloak:keycloak-2

When a stack has integrations, moving it alone would break connections:

Terminal window
# Example graph: keycloak -> postgres <- nextcloud
# Moving postgres alone breaks both keycloak and nextcloud

Use --all-connections to move the entire connected component:

Terminal window
# Moves postgres AND all stacks connected to it
shc move postgres postgres --node node-abc123 --all-connections
# Output:
# Moving 3 connected stacks to node-abc123
# - postgres (primary)
# - keycloak (consumer)
# - nextcloud (consumer)
# v Backing up all stacks (consistency_id: abc123)
# v Uninstalling stacks in dependency order
# v Recovering stacks on node-abc123
# Move complete
Terminal window
# Move from default to staging environment
shc move myapp myapp --node node-abc123 --target-environment staging

Cross-tenant moves require system-level configuration to enable:

Terminal window
# Move stack to different tenant
shc move myapp myapp --node node-abc123 --target-tenant client-org

This uses backup export/import internally to transfer data between tenants.

shc move traefik traefik --node <other> is refused with X400615CLNENT. This is deliberate, and the refusal is not something to work around.

Traefik is the cluster’s single ingress: it binds :80/:443 on its host, and in the default topology (no load balancer in front) every managed DNS record — each deployment’s host, SHC_HOST, and every system.apps.<name>.host — resolves to the public address of the node Traefik currently runs on. Its ACME store and the served leaf certificates materialize on that host too. A move relocates the containers but performs no DNS re-point, so the records would keep pointing at a node that no longer answers on :80/:443.

To relocate the entry point, reinstall it pinned to the target node:

Terminal window
shc install traefik -t master --node node-abc123

The leader’s ingress reconcile (WatchSystemIngress) notices the placement change and re-points every managed record itself. Two things to check first:

  • The target node must have a public address — shc node update <node> --public-address <ip> — or the DNS ladder fails closed and registers nothing.
  • Expect a brief ingress interruption spanning the reconcile tick plus the DNS TTL.

A cluster fronted by a load balancer is unaffected by the node change: when the Traefik deployment carries a conn@80/443 mapping, DNS follows the VIP rather than the node. The refusal still fires — re-point the LB backend instead.

shc node drain relocates workloads through this same move path, so the Traefik deployment is reported in the drain’s failed list with the message above while every other stack migrates. Reinstall the entry point on its new node, then re-run the drain.

The move operation follows four phases:

  • Validate target node exists and is healthy
  • Check quota availability on target
  • Detect integration dependencies
  • Require --all-connections if stack has dependencies/dependants
  • Check for name collisions in target scope
  • Parse --rename dependency mappings (e.g., postgres:postgres-new)
  • Create coordinated backup via BackupOrchestrator
  • Generate shared consistency_id for all stacks
  • If --all-connections: backup entire integration graph
  • For cross-tenant: export backup to target tenant
  • Uninstall stacks in dependency-safe order (dependants first)
  • Clean up from source node
  • Deploy to target node
  • Apply rename mappings
  • Restore integrations automatically
  • Verify all stacks healthy
FlagBehavior
(none)Move single stack; error if has connections
--all-connectionsMove entire integration graph atomically
ConstraintBehavior
--node optionalRequired for cross-node moves; omit for same-node rename
Cross-tenantRequires system-level config to enable
Name collisionError if target scope has stack with same name
Integration dependenciesRequires --all-connections or error
Volume portabilityDATA mounts transferred via backup/recover mechanism
Terminal window
$ shc move nextcloud nextcloud --node node-abc123
Error: Stack 'nextcloud' has dependencies that would be orphaned:
- postgres (provides: postgres-socket)
Use --all-connections to move the entire integration graph, or
unplug dependencies first with: shc unplug nextcloud postgres-socket
$ shc move postgres postgres --node node-abc123
Error: Target node 'node-abc123' already has a stack named 'postgres'.
Use a different name: shc move postgres postgres-v2 --node node-abc123

For cross-tenant moves, backups are transferred via export/import:

Terminal window
# Manual equivalent of cross-tenant move:
# 1. Export backup to file
shc backup export --id abc123 --file /tmp/myapp.bak
# 2. Import backup into target tenant
shc backup import --input /tmp/myapp.bak --tenant other-org
# 3. Recover from imported backup
shc recover -f /tmp/myapp.bak --node node-abc123

Preview the move operation without executing:

Terminal window
$ shc move postgres postgres --node node-abc123 --dry-run
Dry run - no changes will be made
Move Plan:
Source: postgres (node-xyz789)
Target: node-abc123
Actions:
1. Backup postgres (estimated: 2.3 GB)
2. Uninstall postgres from node-xyz789
3. Recover postgres on node-abc123
Quota Impact:
- node-abc123: +1000m CPU, +2GB memory
- node-xyz789: -1000m CPU, -2GB memory
Use --dry-run=false to execute