diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md new file mode 100644 index 0000000..4f53bec --- /dev/null +++ b/docs/guides/canister-management/canister-migration.md @@ -0,0 +1,304 @@ +--- +title: "Canister migration" +description: "Move a canister to a different subnet while preserving its state, with or without keeping the original canister ID" +sidebar: + order: 10 +--- + +Moving a canister to a different subnet is sometimes necessary: the canister was deployed to the wrong subnet, geographic or replication requirements have changed, or you need to consolidate canisters for efficient inter-canister calls. This guide covers both migration paths depending on whether you can accept a new canister ID. + +## When to migrate + +Consider migrating a canister when: + +- **Wrong subnet**: the canister was deployed to an unintended subnet. See [Subnet selection](subnet-selection.md) for how to target subnets at deployment time. +- **Geographic requirements**: a subnet in a specific region is now required for data residency compliance. +- **Replication needs**: moving to a larger subnet (such as the fiduciary subnet) for stronger fault tolerance. +- **Colocation**: consolidating canisters onto the same subnet to reduce inter-canister call latency. + +## Choosing your approach + +Your options depend on whether the canister ID can change: + +| Approach | State | Canister ID | Source canister | Complexity | +|---|---|---|---|---| +| [Snapshot transfer](#migrating-without-preserving-the-canister-id) | Preserved | New ID | Retained | Moderate | +| [Full migration](#migrating-with-the-canister-id) | Preserved | Preserved | Deleted | Advanced | + +**Snapshot transfer** is the simpler path and is appropriate when you can accept a new canister ID. Create a new canister on the desired subnet, transfer state via snapshots, and switch over. The source canister is retained and can be deleted afterward. + +**Full migration** is required when the canister ID must be preserved. The canister ID must not change when: + +- **Threshold signatures (tECDSA / tSchnorr)**: The IC derives signing keys by cryptographically binding them to the calling canister's principal. Any Bitcoin or Ethereum addresses derived from those keys are permanently tied to the original canister ID. Changing the ID means losing access to those signing keys and any assets they control. +- **vetKeys**: vetKey derivation includes the canister's principal. A new ID produces entirely different decryption keys, making previously encrypted data permanently inaccessible. +- **External references**: Other canisters, frontends, or off-chain systems that reference the canister by ID will break. This includes Internet Identity: users who authenticated via a canister-ID-based domain (for example, `.icp0.io`) will lose access to their sessions. + +:::danger +If your canister uses threshold signatures (tECDSA / tSchnorr) or vetKeys, snapshot transfer splits state from keys: the target canister gets a new ID and therefore different signing and decryption keys. Any Bitcoin or Ethereum addresses and any encrypted data tied to the original canister ID become inaccessible from the new canister. + +You still have a recovery window: the source canister is retained after snapshot transfer, so the original keys remain accessible through it. Stop the target, switch back to the source, and perform full migration instead. Do this before deleting the source canister. Once the source is deleted, those keys and any assets or data tied to them are permanently gone. +::: + +## Migrating without preserving the canister ID + +Use this approach when you can accept a new canister ID. The source canister remains on its original subnet until you explicitly delete it; there is no irreversible step and no minimum cycle requirement. + +### 1. Create a target canister + +Create a new canister on the desired subnet. The `--detached` flag creates the canister without recording it in your project configuration, which is useful here since this is a temporary migration target: + +```bash +icp canister create --detached -e ic --subnet +``` + +Note the canister ID printed in the output. Add `--quiet` to print only the ID, which is useful for scripting. + +### 2. Transfer state via snapshots + +See [Canister snapshots](snapshots.md#downloading-and-uploading-snapshots) for full details on resuming interrupted transfers. + +```bash +# Stop and snapshot the source canister +icp canister stop my-canister -e ic +icp canister snapshot create my-canister -e ic + +# Download the snapshot locally +icp canister snapshot download my-canister -o ./migration-snapshot -e ic + +# Upload and restore on the target canister +icp canister snapshot upload -i ./migration-snapshot -n ic +icp canister snapshot restore -n ic +``` + +### 3. Copy settings + +Snapshots capture the Wasm module and memory, but not canister settings. Check the source canister's current settings and apply any non-default values to the target: + +```bash +icp canister settings show my-canister -e ic + +# Apply non-default settings to the target canister +icp canister settings update \ + --compute-allocation 10 \ + --freezing-threshold 604800 \ + -n ic +``` + +Run `icp canister settings update --help` for a full list of available settings. + +### 4. Switch over + +Start the target canister: + +```bash +icp canister start -n ic +``` + +The source canister is still stopped on its original subnet. Manage it before updating the project mapping, while `my-canister` still refers to it: + +```bash +# Delete it if no longer needed +icp canister delete my-canister -e ic +``` + +Update your project to point `my-canister` to the new ID. icp-cli stores canister IDs in `.icp/data/mappings/.ids.json` (mainnet) or `.icp/cache/mappings/.ids.json` (local). Edit the file: + +```json +{ + "my-canister": "" +} +``` + +Update any other canisters, frontends, or off-chain systems that reference the old canister ID. + +## Migrating with the canister ID + +Use this approach when the canister ID must be preserved. This adds an ID migration step using `icp canister migrate-id`, which moves the canister ID from the source to the target on the new subnet. + +> **Important:** `icp canister migrate-id` moves only the canister ID. It does **not** transfer state, settings, or cycles. If you skip the preparation steps below, the canister's Wasm module, memory, and stable memory will be lost. The source canister is permanently deleted and its cycles are burned when the migration completes. + +### How the ID migration works + +`icp canister migrate-id` tells the NNS migration canister to: + +1. Rename the target canister to have the source canister's ID +2. Update the IC routing table so the source canister ID now resolves to the target's subnet +3. Delete the source canister from its original subnet; all remaining cycles are burned +4. Restore the source canister's original controllers on the target + +After this process: + +- **Source canister**: permanently deleted; its cycles are burned and its ID now lives on the target's subnet +- **Target canister**: continues on the same subnet under the source canister's ID, with the state, cycles, and settings it had before migration (controllers are replaced by those 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`. + +### 1. Create a target canister + +Create a new canister on the desired subnet: + +```bash +icp canister create --detached -e ic --subnet +``` + +Note the canister ID from the output. Immediately top up the target canister with enough cycles for ongoing operation. The source canister's cycles are burned during migration and are not transferred: + +```bash +icp canister top-up --amount 5T -n ic +``` + +### 2. Transfer state via snapshots + +Stop the source canister, create a snapshot, download it, upload it to the target, and restore it: + +```bash +# Stop and snapshot the source canister +icp canister stop my-canister -e ic +icp canister snapshot create my-canister -e ic + +# Download the snapshot locally +icp canister snapshot download my-canister -o ./migration-snapshot -e ic + +# Upload the snapshot to the target canister +icp canister snapshot upload -i ./migration-snapshot -n ic + +# Restore on the target (use the new snapshot ID from the upload output) +icp canister snapshot restore -n ic +``` + +After restoring, the target has the same Wasm module, memory, and stable memory as the source. + +**Delete the snapshot on the target.** The `migrate-id` command requires the target to have no snapshots before it will proceed: + +```bash +icp canister snapshot delete -n ic +``` + +For large canisters, downloads and uploads may take time. If interrupted, resume with `--resume`. See [Canister snapshots](snapshots.md#downloading-and-uploading-snapshots) for details. + +### 3. Copy settings + +Snapshots capture the Wasm module and memory, but not canister settings. Controllers are automatically restored from the source during ID migration, but other settings must be copied manually: + +```bash +icp canister settings show my-canister -e ic + +# Apply non-default settings to the target (controllers are restored automatically; do not copy them) +icp canister settings update \ + --compute-allocation 10 \ + --freezing-threshold 604800 \ + --wasm-memory-limit 2GiB \ + -n ic +``` + +### 4. Stop the target canister + +Both canisters must be stopped before the ID migration. The source is already stopped from step 2, so only the target needs stopping: + +```bash +icp canister stop -n ic +``` + +### 5. Migrate the canister ID + +Run the migration. The `--replace` flag accepts canister names or principals: + +```bash +icp canister migrate-id my-canister --replace -e ic +``` + +The command validates prerequisites (different subnets, both 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, and polls for completion. + +> **Cycles warning:** The source canister requires a minimum cycle balance before migration can proceed. All remaining cycles on the source are burned when it is deleted. If the source has a large cycle balance, consider reducing it before migrating. The command warns you if the balance is high enough to warrant attention. + +### 6. Start and verify + +Start the canister to resume operation: + +```bash +icp canister start my-canister -e ic +``` + +Verify the canister is on the expected subnet by querying the NNS Registry canister: + +```bash +icp canister call rwlgt-iiaaa-aaaaa-aaaaa-cai get_subnet_for_canister \ + '(record { "principal" = opt principal "" })' --query -n ic +``` + +### 7. Clean up + +The NNS migration canister is added as a controller during ID migration and is not automatically removed. Remove it if you want a clean controller set: + +```bash +# Check current controllers +icp canister settings show my-canister -e ic + +# Remove the NNS migration canister +icp canister settings update my-canister --remove-controller sbzkb-zqaaa-aaaaa-aaaiq-cai -e ic +``` + +Delete the local snapshot directory once you have verified the migration succeeded: + +```bash +rm -rf ./migration-snapshot +``` + +### Handling interruptions + +If the `migrate-id` command is interrupted or times out (the default timeout is 12 minutes), the migration continues on the network. Use `--resume-watch` to reconnect: + +```bash +icp canister migrate-id my-canister --replace --resume-watch -e ic +``` + +This skips validation and initiation and resumes polling migration status. To exit early without waiting, use `--skip-watch` and then `--resume-watch` later to verify completion. + +## Troubleshooting + +### "Canister is not ready for migration" + +The canister has not finished preparing. Wait a few seconds and retry. + +### "Canisters are on the same subnet" + +`migrate-id` requires canisters on different subnets. Create a new target on the desired subnet: + +```bash +icp canister create --detached -e ic --subnet +``` + +### "Target canister has snapshots" + +Delete all snapshots on the target before running `migrate-id`: + +```bash +icp canister snapshot list -n ic +icp canister snapshot delete -n ic +``` + +### Insufficient cycles on source + +The source canister must meet a minimum cycle balance for migration. Top it up: + +```bash +icp canister top-up my-canister --amount 1T -e ic +``` + +### Migration timed out + +The 12-minute timeout does not cancel the migration. Use `--resume-watch` to continue monitoring: + +```bash +icp canister migrate-id my-canister --replace --resume-watch -e ic +``` + +## Next steps + +- [Subnet selection](subnet-selection.md): Choose the right subnet at deployment time to avoid needing to migrate +- [Canister snapshots](snapshots.md): Full reference for creating, downloading, uploading, and restoring snapshots +- [Canister settings](settings.md): Settings that snapshots do not capture and that must be copied manually +- [Cycles management](cycles-management.md): Understand cycle costs before and after migration + + diff --git a/docs/guides/canister-management/cycles-management.mdx b/docs/guides/canister-management/cycles-management.mdx index 129b20c..c5ed805 100644 --- a/docs/guides/canister-management/cycles-management.mdx +++ b/docs/guides/canister-management/cycles-management.mdx @@ -2,7 +2,7 @@ title: "Cycles management" description: "Acquire cycles, monitor canister balances, set freezing thresholds, and deploy to mainnet." sidebar: - order: 7 + order: 4 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/docs/guides/canister-management/large-wasm.md b/docs/guides/canister-management/large-wasm.md index b1e5898..a532fb2 100644 --- a/docs/guides/canister-management/large-wasm.md +++ b/docs/guides/canister-management/large-wasm.md @@ -2,7 +2,7 @@ title: "Large Wasm modules" description: "Deploy canisters that exceed the 2 MiB Wasm limit using chunk store and compression" sidebar: - order: 9 + order: 8 --- ICP enforces a 2 MiB message size limit that applies to Wasm modules uploaded via `install_code`. Canisters with complex business logic, embedded ML models, or large dependency trees often exceed this threshold. There are two complementary approaches: reduce the module size with compression and dead-code stripping, or bypass the limit entirely by uploading the module in chunks. diff --git a/docs/guides/canister-management/lifecycle.mdx b/docs/guides/canister-management/lifecycle.mdx index e637c1c..07d7454 100644 --- a/docs/guides/canister-management/lifecycle.mdx +++ b/docs/guides/canister-management/lifecycle.mdx @@ -26,7 +26,7 @@ In practice, `icp deploy` handles steps 1–3 automatically. You interact with i ## Create a canister -Creating a canister registers an empty placeholder on the network. The canister receives a unique ID (a [principal](../../concepts/canisters.md)) but has no code yet. +Creating a canister registers an empty placeholder on the network. The canister receives a unique ID (a [principal](../../concepts/principals.md)) but has no code yet. ```bash icp canister create my-canister @@ -280,27 +280,7 @@ Remaining cycles are refunded to the controller who made the delete request. ## Migrate a canister between subnets -Sometimes you need to move a canister to a different [subnet](../../concepts/network-overview.md#subnets). Common reasons include: - -- **Wrong subnet**: the canister was deployed to an unintended subnet -- **Geographic requirements**: data residency rules require a specific region -- **Replication needs**: moving to a larger subnet for higher fault tolerance -- **Colocation**: consolidating canisters onto the same subnet for efficient inter-canister calls - -There are two approaches, depending on whether you need to keep the canister ID: - -| Approach | State | Canister ID | When to use | -|----------|-------|-------------|-------------| -| **Snapshot transfer** | Preserved | New ID | Default: simpler and safer | -| **Full migration** | Preserved | Preserved | When the canister ID is load-bearing | - -Preserving the canister ID matters when: - -- **Threshold signatures (tECDSA/tSchnorr)**: signing keys are cryptographically bound to the canister's principal. A new ID means losing access to derived keys and any assets they control on other blockchains. -- **VetKeys**: decryption keys are derived from the canister ID. A new ID makes previously encrypted data inaccessible. -- **External references**: other canisters, frontends, or off-chain systems reference the canister by ID. This includes Internet Identity sessions tied to a canister-ID-based domain. - -Both approaches use [canister snapshots](snapshots.md) to transfer state. For the complete step-by-step procedure, see the [icp-cli canister migration guide](https://github.com/dfinity/icp-cli/blob/main/docs/guides/canister-migration.md). +If a canister ends up on the wrong subnet, or you need to move it for geographic, replication, or colocation reasons, see the [Canister migration](canister-migration.md) guide. It covers both approaches: snapshot transfer (simpler, new canister ID) and full migration using `icp canister migrate-id` (preserves the original ID). ## Programmatic canister management @@ -438,6 +418,8 @@ The IC decompresses the module automatically during installation. For strategies - [Cycles management](cycles-management.md): fund canisters and monitor cycle consumption - [Data persistence](../backends/data-persistence.md): deep dive into stable memory and persistence strategies - [Canister snapshots](snapshots.md): create backups before risky upgrades +- [Subnet selection](subnet-selection.md): choose which subnet a canister is created on +- [Canister migration](canister-migration.md): move a canister to a different subnet after deployment - [Upgrade safety](../security/canister-upgrades.md): security considerations for safe upgrades - [Testing strategies](../testing/strategies.md): test lifecycle operations locally diff --git a/docs/guides/canister-management/optimization.md b/docs/guides/canister-management/optimization.md index b3a7945..7336068 100644 --- a/docs/guides/canister-management/optimization.md +++ b/docs/guides/canister-management/optimization.md @@ -2,7 +2,7 @@ title: "Canister optimization" description: "Reduce Wasm binary size and improve canister performance with ic-wasm, SIMD, performance counters, and memory tuning" sidebar: - order: 4 + order: 6 --- Canister Wasm binaries compiled from Rust or Motoko are often larger than necessary and may execute more instructions than needed. Smaller binaries install faster, consume fewer [cycles](../../concepts/cycles.md) on deployment, and leave more room within the per-canister Wasm memory limit. Better runtime efficiency directly reduces the cycles charged per call. diff --git a/docs/guides/canister-management/reproducible-builds.md b/docs/guides/canister-management/reproducible-builds.md index c8ff89e..721bf5c 100644 --- a/docs/guides/canister-management/reproducible-builds.md +++ b/docs/guides/canister-management/reproducible-builds.md @@ -2,7 +2,7 @@ title: "Reproducible builds" description: "Verify that deployed canister Wasm matches the source code using deterministic builds" sidebar: - order: 6 + order: 7 --- A reproducible build produces the same WebAssembly module byte-for-byte whenever anyone compiles the same source code in the same documented environment. For canisters, this matters because ICP lets anyone query a canister's Wasm hash: but only a reproducible build makes that hash meaningful. Without it, a published hash cannot be linked to readable source code. diff --git a/docs/guides/canister-management/snapshots.md b/docs/guides/canister-management/snapshots.md index ab8002f..6ae039b 100644 --- a/docs/guides/canister-management/snapshots.md +++ b/docs/guides/canister-management/snapshots.md @@ -147,7 +147,7 @@ icp canister start my-canister -e ic ## Example: transferring state between canisters -Download a snapshot from a source canister and upload it to a target canister. This download-then-upload workflow is the foundation of canister migration between subnets: direct restore (`load_canister_snapshot`) only works within the same subnet, so cross-subnet transfer requires downloading the snapshot locally first and uploading it to the target. +Download a snapshot from a source canister and upload it to a target canister. This download-then-upload workflow is the foundation of canister migration between subnets: a snapshot must exist on the target canister before it can be restored, so transferring state to a different canister requires downloading the snapshot locally first and uploading it to the target. All snapshot commands accept either canister names (with `-e`) or canister IDs (with `-n`). Use `-n ic` when the target canister is not part of your project. @@ -179,6 +179,7 @@ icp canister status my-canister -e ic ## Next steps - [Canister lifecycle](lifecycle.md): Understand how snapshots fit into the upgrade workflow +- [Canister migration](canister-migration.md): Complete guide for moving a canister to a different subnet using the snapshot transfer workflow - [Canister upgrades security](../security/canister-upgrades.md): Security considerations when using snapshot-based rollbacks - [icp-cli canister snapshot reference](https://cli.internetcomputer.org/0.2/guides/canister-snapshots): Full command reference for all snapshot subcommands diff --git a/docs/guides/canister-management/subnet-selection.md b/docs/guides/canister-management/subnet-selection.md index b1412c4..48481e0 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -2,7 +2,7 @@ title: "Subnet selection" description: "Choose the right subnet for your canister deployment based on geographic, security, and colocation requirements" sidebar: - order: 8 + order: 9 --- The Internet Computer is composed of independent [subnets](../../concepts/network-overview.md#subnets): each a blockchain that hosts [canisters](../../concepts/canisters.md) and runs its own consensus. By default, icp-cli selects a subnet automatically when you deploy. This guide explains when and how to target a specific subnet. @@ -84,15 +84,48 @@ The `--subnet` flag only affects canister creation. If a canister already exists > **Tip:** Subnet principal IDs can change over time. Always verify the current ID for a named subnet on the [ICP Dashboard](https://dashboard.internetcomputer.org/subnets) before using it in production scripts. -## Colocation with an existing canister +## Colocation via proxy canister -To create a new canister on the same subnet as an existing canister, use the `--proxy` flag with `icp canister create`. This routes the creation call through the existing canister and places the new canister on the same subnet: +To create a new canister on the same subnet as an existing canister, use the `--proxy` flag with `icp canister create`. This routes the creation call through a proxy canister, and the new canister is placed on that proxy's subnet: ```bash -icp canister create my_new_canister -e ic --proxy +icp canister create my_new_canister -e ic --proxy + +# With a custom cycle allocation for the new canister (proxy pays from its own balance): +icp canister create my_new_canister -e ic --proxy --cycles 3T ``` -This is useful when you already have a deployed canister and want new canisters to live on the same subnet. The proxy canister must implement a `proxy` method that forwards management canister calls. +`--proxy` and `--subnet` are mutually exclusive: the CLI rejects any call that specifies both. + +### Proxy interface requirement + +The target canister must expose a `proxy` method with this exact Candid interface. An arbitrary canister will reject the call: + +```candid +type ProxyArgs = record { + canister_id : principal; + method : text; + args : blob; + cycles : nat; +}; + +type ProxyResult = variant { + Ok : record { result : blob }; + Err : variant { + InsufficientCycles : record { available : nat; required : nat }; + CallFailed : record { reason : text }; + UnauthorizedUser; + }; +}; + +service : { + proxy : (ProxyArgs) -> (ProxyResult); +} +``` + +### Cycles model + +The `--cycles` value specifies how many cycles to allocate to the new canister. Those cycles are drawn from the proxy canister's own balance, not from your identity's balance on the cycles ledger. Ingress messages on ICP cannot carry cycles; the value is passed as data in `ProxyArgs.cycles`, and the proxy spends from its own cycle balance when forwarding the management canister call. Ensure the proxy canister is adequately funded before use. ## Storage capacity considerations @@ -110,10 +143,10 @@ Verify the subnet ID is correct. Some subnets (including all system subnets) do ### Canister is on the wrong subnet -Canisters cannot be moved between subnets while keeping the same canister ID by default. Your options depend on whether you can accept a new ID: +Canisters cannot be moved between subnets while keeping the same canister ID without using `icp canister migrate-id`. Your options depend on whether you can accept a new ID: - **New canister ID is acceptable**: Transfer state via [canister snapshots](snapshots.md) to a new canister on the correct subnet. -- **Canister ID must be preserved**: Transfer state via snapshots, copy settings, then use `icp canister migrate-id` to move the ID to the new canister. +- **Canister ID must be preserved**: Use `icp canister migrate-id` to move the ID to a new canister on the correct subnet. See the [canister migration guide](canister-migration.md#migrating-with-the-canister-id) for the complete step-by-step workflow. Note that any canister ID change means losing access to any threshold signature keys (tECDSA, tSchnorr) and vetKeys derived by the original canister: these are cryptographically bound to the canister ID. Any assets or encrypted data tied to those keys become permanently inaccessible under the new ID. @@ -121,7 +154,8 @@ Note that any canister ID change means losing access to any threshold signature - [Cycles costs](../../references/cycles-costs.md#replication-factors): Cost tables and the subnet multiplier formula - [Subnet types reference](../../references/subnet-types.md): Full reference for all subnet types with node counts and properties -- [Canister snapshots](snapshots.md): Transfer state between canisters when migrating subnets +- [Canister snapshots](snapshots.md#example-transferring-state-between-canisters): Download/upload workflow for transferring state to another canister +- [Canister migration](canister-migration.md): Complete workflow for moving a canister to a different subnet, with or without preserving the canister ID - [Network overview](../../concepts/network-overview.md): How subnets fit into the ICP architecture - +