Canister Migration
Move a canister to a different subnet. Depending on your needs, you can preserve just the canister’s state, or both its state and canister ID.
When to Migrate
- Wrong subnet — A canister was deployed to an unintended subnet
- Geographic requirements — Moving to a subnet in a specific region for data residency
- Replication needs — Moving to a larger subnet for higher fault tolerance
- Colocation — Consolidating canisters onto the same subnet for efficient inter-canister calls
Choosing Your Approach
| Approach | State | Canister ID | Source Canister | Complexity |
|---|---|---|---|---|
| Snapshot transfer | Preserved | New ID | Retained | Moderate |
| Full migration (snapshot transfer + ID migration) | Preserved | Preserved | Deleted | Advanced |
Snapshot transfer — When you can accept a new canister ID. Create a new canister on the desired subnet, transfer state via snapshots, and switch over. See Migrating Without Preserving the Canister ID below.
Full migration — When the canister ID must be preserved. This applies when the canister ID is load-bearing:
- Threshold signatures (tECDSA / tSchnorr): The IC derives signing keys by cryptographically binding them to the calling canister’s principal. A canister’s derived keys — and any addresses or public keys derived from them — are permanently tied to its ID. Losing the ID means losing access to those keys and any assets they control, whether those are addresses on other blockchains (Bitcoin, Ethereum, etc.) or ICP principals controlled by the canister.
- VetKeys: VetKey derivation similarly includes the canister’s principal. A new ID produces entirely different decryption keys, making previously encrypted data inaccessible.
- External references: Other canisters, frontends, or off-chain systems that reference the canister by ID would break. This includes Internet Identity — users who authenticated via a canister-ID-based domain (e.g.,
<canister-id>.icp0.io) will lose access to their sessions.
See Migrating With the Canister ID below for the full workflow.
Migrating Without Preserving the Canister ID
If you don’t need to keep the canister ID, you can move state to a new canister using snapshots. This avoids the complexity of ID migration — no NNS migration canister, no cycle burn on the source, no minimum cycle requirement.
1. Create a New Canister
Create a new canister on the desired subnet. The --detached flag allows creating a canister without recording it in your project configuration — useful here because this is a temporary target for state transfer:
icp canister create --detached -n ic --subnet <target-subnet-id>Note the canister ID from the output — you’ll use it in subsequent steps. Add --quiet to print just the canister ID (useful for scripting).
2. Transfer State via Snapshots
# Stop and snapshot the source canistericp canister stop my-canister -e icicp canister snapshot create my-canister -e ic
# Download the snapshot locallyicp canister snapshot download my-canister <snapshot-id> -o ./migration-snapshot -e ic
# Upload and restore on the new canistericp canister snapshot upload <target-id> -i ./migration-snapshot -n icicp canister snapshot restore <target-id> <new-snapshot-id> -n icSee Canister Snapshots for details on resuming interrupted transfers.
3. Copy Settings
Snapshots capture WASM module and memory, but not canister settings. Check your source canister’s settings and apply any non-default values to the new canister:
icp canister status my-canister -e ic
# Example: copy non-default settingsicp canister settings update <target-id> \ --compute-allocation 10 \ --freezing-threshold 604800 \ -n icRun icp canister settings update --help for a full list of available settings. Common ones include compute allocation, memory allocation, and freezing threshold.
4. Switch Over
Start the new canister:
icp canister start <target-id> -n icThe old canister still exists on its original subnet (stopped since step 2) and can be repurposed or deleted. Manage it before updating the project mapping, while my-canister still refers to it:
# Delete it if no longer neededicp canister delete my-canister -e icUpdate your project to use the new canister going forward. icp-cli stores canister IDs per environment in .icp/data/mappings/<environment>.ids.json (for connected networks like mainnet) or .icp/cache/mappings/<environment>.ids.json (for managed networks). Update the mapping so my-canister points to the new canister’s ID:
{ "my-canister": "<target-id>"}Update external references — any other canisters, frontends, or off-chain systems that reference the old canister ID need to be updated to the new ID.
Migrating With the Canister ID
When you need to preserve the canister ID, the process adds an ID migration step after transferring state. This uses icp canister migrate-id to move the canister ID from the source to the target canister on the new subnet.
Important: The
migrate-idcommand only moves the canister ID — it does not transfer state, settings, or cycles. If you skip the preparation steps, your canister’s WASM module, memory, and stable memory will be lost. Follow the full workflow below.
How the ID Migration Works
Under the hood, icp canister migrate-id tells the NNS migration canister to:
- Rename the target canister to have the source canister’s ID
- Update the IC routing table so the source canister ID now resolves to the target’s subnet
- Delete the source canister from its original subnet (all remaining cycles are burned)
- Restore the source canister’s original controllers on the target
After this process:
- Source canister — Permanently deleted. Its cycles are burned and its canister ID now lives on the target’s subnet.
- Target canister — Continues to exist on the same subnet, but now under the source canister’s ID. It retains its own state, cycles, and settings (except controllers, which are restored from the source).
- Target canister’s original ID — Ceases to exist permanently.
Because the target canister’s state is what survives, you must transfer state via snapshots before running migrate-id. You should also copy any non-default settings and ensure the target has sufficient cycles for ongoing operation.
1. Create a Target Canister
Create a new canister on the desired subnet. The --detached flag allows creating a canister without recording it in your project configuration:
icp canister create --detached -n ic --subnet <target-subnet-id>Note the canister ID from the output — you’ll use it in all subsequent steps. Add --quiet to print just the canister ID (useful for scripting).
Top up the target canister with enough cycles for ongoing operation, since the source canister’s cycles will be burned during the ID migration:
icp canister top-up <target-id> --amount 5T -n ic2. Transfer State via Snapshots
Stop the source canister and create a snapshot, then download it, upload it to the target, and restore it:
# Stop and snapshot the source canistericp canister stop my-canister -e icicp canister snapshot create my-canister -e ic# Note the snapshot ID from the output
# Download the snapshot locallyicp canister snapshot download my-canister <snapshot-id> -o ./migration-snapshot -e ic
# Upload the snapshot to the target canistericp canister snapshot upload <target-id> -i ./migration-snapshot -n ic
# Restore the snapshot on the target canister (use the new snapshot ID from the upload output)icp canister snapshot restore <target-id> <new-snapshot-id> -n icAfter restoring, the target canister has the same WASM module, memory, and stable memory as the source.
Delete the snapshot on the target — the ID migration requires the target to have no snapshots:
icp canister snapshot delete <target-id> <new-snapshot-id> -n icFor large canisters, downloads and uploads may take time. If interrupted, resume with the --resume flag. See Canister Snapshots for details.
3. Copy Settings
Snapshots capture WASM module and memory, but not canister settings. Controllers are automatically restored from the source during the ID migration, but other settings need to be copied manually.
Check your source canister’s current settings:
icp canister status my-canister -e icIf any settings differ from the defaults, apply them to the target canister:
# Example: copy non-default settings to the target canistericp canister settings update <target-id> \ --compute-allocation 10 \ --freezing-threshold 604800 \ --wasm-memory-limit 2GiB \ -n icRun icp canister settings update --help for a full list of available settings. Common ones include compute allocation, memory allocation, and freezing threshold. You do not need to copy controllers — those are restored automatically.
4. Stop the Target Canister
Both canisters must be stopped before the ID migration. The source canister is already stopped from step 2, so only the target needs stopping:
icp canister stop <target-id> -n ic5. Migrate the Canister ID
Run the ID migration. The --replace flag accepts both canister names and canister IDs:
icp canister migrate-id my-canister --replace <target-id> -e icThis command:
- Validates that both canisters meet the prerequisites (different subnets, stopped, sufficient cycles, no snapshots on target)
- Asks for confirmation (skip with
-y) - Adds the NNS migration canister as a controller of both canisters
- Initiates the migration through the NNS migration canister
- Polls migration status until complete
Cycles warning: The source canister requires a minimum cycle balance for migration. All remaining cycles on the source canister are burned when it is deleted — they are not transferred to the target. If your source canister has a large cycle balance, consider reducing it before migrating. The command will warn you if the balance is high enough to warrant attention.
6. Wait for Completion
The command automatically polls for status and displays progress. Migration typically completes within a few minutes, but the command will wait up to 12 minutes before timing out.
On success, the source canister’s ID now lives on the target’s subnet with the state you transferred earlier. The source canister on the original subnet is permanently deleted.
7. Start and Verify
Start the canister to resume operation:
icp canister start my-canister -e icVerify the canister is on the expected subnet by querying the NNS Registry canister:
icp canister call rwlgt-iiaaa-aaaaa-aaaaa-cai get_subnet_for_canister \ '(record { "principal" = opt principal "<canister-id>" })' --query -n ic8. Clean Up
Remove the NNS migration canister as controller if desired — it is added during the ID migration and not automatically removed:
# Check controllersicp canister status my-canister -e ic
# Remove the NNS migration canister as controllericp canister settings update my-canister --remove-controller sbzkb-zqaaa-aaaaa-aaaiq-cai -e icDelete local snapshot files — remove the ./migration-snapshot directory once you’ve verified the migration succeeded.
Handling Interruptions
If the migrate-id command is interrupted or times out, the ID migration continues on the network. Use these flags to manage it:
Resume watching:
icp canister migrate-id my-canister --replace <target-id> --resume-watch -e icThis skips validation and initiation, and resumes polling the migration status.
Exit early:
icp canister migrate-id my-canister --replace <target-id> --skip-watch -e icThis exits early once the migration reaches an intermediate state, without waiting for full completion. Use --resume-watch later to verify the migration finished successfully.
Troubleshooting
“Canister is not ready for migration”
The canister hasn’t finished preparing for migration. Wait a few seconds and try again.
“Canisters are on the same subnet”
Migration requires canisters on different subnets. Create a new canister on the desired subnet to use as the migration target:
icp canister create --detached -n ic --subnet <target-subnet-id>“Target canister has snapshots”
Delete all snapshots on the target canister first:
icp canister snapshot list <target-id> -n icicp canister snapshot delete <target-id> <snapshot-id> -n icInsufficient cycles
Top up the source canister to meet the minimum balance required for migration:
icp canister top-up my-canister --amount 1T -e icMigration timed out
The 12-minute timeout doesn’t cancel the migration. Rerun with --resume-watch to continue watching:
icp canister migrate-id my-canister --replace <target-id> --resume-watch -e icNext Steps
- Canister Snapshots — Full snapshot reference (download, upload, restore)
- Deploying to Specific Subnets — Choose which subnet to deploy to