From 9d2b0adb6ee96d969ddd1768f637cdc49536b78a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 12:45:49 +0200 Subject: [PATCH 01/10] docs(canister-management): improve subnet selection guide Addresses all issues raised in #233: - Rename "Colocation with an existing canister" to "Colocation via proxy canister" and expand it with three targeted fixes: - Clarify that --proxy and --subnet are mutually exclusive - Add the full ProxyArgs/ProxyResult Candid interface so developers know exactly what their proxy canister must implement - Explain the cycles model: --cycles passes a value through ProxyArgs.cycles and is paid from the proxy canister's own balance, not the caller's wallet - Fix the "by default" phrasing in troubleshooting: replace with "without using icp canister migrate-id" - Add a direct link to the canister migration guide in the troubleshooting entry and in Next steps --- .../canister-management/subnet-selection.md | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/guides/canister-management/subnet-selection.md b/docs/guides/canister-management/subnet-selection.md index b1412c4..04589ae 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -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 wallet. 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**: Transfer state via snapshots, copy settings, delete the snapshot on the target, then run `icp canister migrate-id` to move the ID to the new canister. See the [canister migration guide](https://cli.internetcomputer.org/0.2/guides/canister-migration) for the complete step-by-step workflow, including the cycle warning, the snapshot deletion requirement before `migrate-id`, NNS controller cleanup, and how to recover from interruptions. 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. @@ -122,6 +155,7 @@ 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 migration](https://cli.internetcomputer.org/0.2/guides/canister-migration): 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 From 3b6d0b34befad6e3f6fe177758eb760d9af09c49 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 12:59:31 +0200 Subject: [PATCH 02/10] docs(canister-management): fix accuracy and precision in subnet selection guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #245 review feedback and source-verified inaccuracies: - Cycles model: replace "from your wallet" with "from your identity's balance on the cycles ledger" (wallet is a deprecated dfx concept) - Migration description: fix step ordering — snapshot deletion on target belongs within the state transfer phase, before copying settings, not after; add the missing "stop the target canister" prerequisite for migrate-id; add the cycles burn warning (source cycles burned, target needs funding in advance) - Next steps snapshot link: add section anchor pointing directly to the state-transfer example; sharpen the label - Upstream comment: add proxy.rs and canister-migration.md references --- docs/guides/canister-management/subnet-selection.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/canister-management/subnet-selection.md b/docs/guides/canister-management/subnet-selection.md index 04589ae..0f70a82 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -125,7 +125,7 @@ service : { ### 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 wallet. 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. +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 @@ -146,7 +146,7 @@ Verify the subnet ID is correct. Some subnets (including all system subnets) do 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, delete the snapshot on the target, then run `icp canister migrate-id` to move the ID to the new canister. See the [canister migration guide](https://cli.internetcomputer.org/0.2/guides/canister-migration) for the complete step-by-step workflow, including the cycle warning, the snapshot deletion requirement before `migrate-id`, NNS controller cleanup, and how to recover from interruptions. +- **Canister ID must be preserved**: Create a target canister on the correct subnet, transfer state via snapshots, delete the snapshot on the target (required before `migrate-id`), copy non-default settings, stop the target canister, then run `icp canister migrate-id`. All cycles remaining on the source canister are burned when it is deleted; top up the target canister in advance. See the [canister migration guide](https://cli.internetcomputer.org/0.2/guides/canister-migration) for the complete step-by-step workflow, including NNS controller cleanup and interruption recovery. 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. @@ -154,8 +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 a canister on a different subnet - [Canister migration](https://cli.internetcomputer.org/0.2/guides/canister-migration): 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 - + From 3da262fe0005097d8f0179671c1c897c4830d0b8 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:04:13 +0200 Subject: [PATCH 03/10] docs(canister-management): add canister migration guide and update cross-references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs/guides/canister-management/canister-migration.md covering both migration paths: snapshot transfer (new canister ID) and full migration with ID preservation via icp canister migrate-id. The guide documents the step ordering that matters for correctness — snapshot deletion on the target before migrate-id, target must be stopped, source cycles are burned — and includes interruption recovery and NNS controller cleanup. Update cross-references: - subnet-selection.md: replace external cli.internetcomputer.org links with internal links to canister-migration.md - snapshots.md: add canister migration to Next steps - large-wasm.md: bump sidebar order from 9 to 10 to accommodate canister-migration at order 9 (after subnet-selection) --- .../canister-management/canister-migration.md | 298 ++++++++++++++++++ docs/guides/canister-management/large-wasm.md | 2 +- docs/guides/canister-management/snapshots.md | 1 + .../canister-management/subnet-selection.md | 4 +- 4 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 docs/guides/canister-management/canister-migration.md diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md new file mode 100644 index 0000000..d657a20 --- /dev/null +++ b/docs/guides/canister-management/canister-migration.md @@ -0,0 +1,298 @@ +--- +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: 9 +--- + +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 is load-bearing: + +| 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. A canister ID is load-bearing in these situations: + +- **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. + +## 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 + +The snapshot download/upload workflow is required for cross-subnet transfers because the direct restore (`load_canister_snapshot`) only works within the same subnet. 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 (do not copy controllers — they are restored automatically) +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/large-wasm.md b/docs/guides/canister-management/large-wasm.md index b1e5898..e3e1092 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: 10 --- 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/snapshots.md b/docs/guides/canister-management/snapshots.md index ab8002f..5aead1a 100644 --- a/docs/guides/canister-management/snapshots.md +++ b/docs/guides/canister-management/snapshots.md @@ -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 download/upload 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 0f70a82..575f4ab 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -146,7 +146,7 @@ Verify the subnet ID is correct. Some subnets (including all system subnets) do 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**: Create a target canister on the correct subnet, transfer state via snapshots, delete the snapshot on the target (required before `migrate-id`), copy non-default settings, stop the target canister, then run `icp canister migrate-id`. All cycles remaining on the source canister are burned when it is deleted; top up the target canister in advance. See the [canister migration guide](https://cli.internetcomputer.org/0.2/guides/canister-migration) for the complete step-by-step workflow, including NNS controller cleanup and interruption recovery. +- **Canister ID must be preserved**: Create a target canister on the correct subnet, transfer state via snapshots, delete the snapshot on the target (required before `migrate-id`), copy non-default settings, stop the target canister, then run `icp canister migrate-id`. All cycles remaining on the source canister are burned when it is deleted; top up the target canister in advance. See the [canister migration guide](canister-migration.md#migrating-with-the-canister-id) for the complete step-by-step workflow, including NNS controller cleanup and interruption recovery. 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. @@ -155,7 +155,7 @@ 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#example-transferring-state-between-canisters): Download/upload workflow for transferring state to a canister on a different subnet -- [Canister migration](https://cli.internetcomputer.org/0.2/guides/canister-migration): Complete workflow for moving a canister to a different subnet, with or without preserving the canister ID +- [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 From 8b21f00070050f717f7e92e81104374304f9ddeb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:14:19 +0200 Subject: [PATCH 04/10] docs(canister-management): tighten cross-references and add danger callout - subnet-selection troubleshooting: shorten the "ID must be preserved" bullet to one sentence + link; detail belongs in the migration guide - canister-migration: add :::danger callout after the load-bearing ID cases to make permanent, irreversible loss visible before users choose an approach - subnet-selection Next steps: tighten snapshot label to "to another canister" (download/upload works for any target, not just cross-subnet) - snapshots Next steps: same label fix --- docs/guides/canister-management/canister-migration.md | 4 ++++ docs/guides/canister-management/snapshots.md | 2 +- docs/guides/canister-management/subnet-selection.md | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index d657a20..a11122e 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -33,6 +33,10 @@ Your options depend on whether the canister ID is load-bearing: - **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 +Choosing snapshot transfer when the canister ID is load-bearing causes permanent, irreversible loss. Any threshold signature keys (tECDSA / tSchnorr) and the Bitcoin or Ethereum addresses derived from them are gone. Any data encrypted under a vetKey becomes permanently inaccessible. There is no recovery path. Verify whether your canister derives threshold signatures or vetKeys before choosing an approach. +::: + ## 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. diff --git a/docs/guides/canister-management/snapshots.md b/docs/guides/canister-management/snapshots.md index 5aead1a..3474dd4 100644 --- a/docs/guides/canister-management/snapshots.md +++ b/docs/guides/canister-management/snapshots.md @@ -179,7 +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 download/upload 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 575f4ab..e0ceb62 100644 --- a/docs/guides/canister-management/subnet-selection.md +++ b/docs/guides/canister-management/subnet-selection.md @@ -146,7 +146,7 @@ Verify the subnet ID is correct. Some subnets (including all system subnets) do 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**: Create a target canister on the correct subnet, transfer state via snapshots, delete the snapshot on the target (required before `migrate-id`), copy non-default settings, stop the target canister, then run `icp canister migrate-id`. All cycles remaining on the source canister are burned when it is deleted; top up the target canister in advance. See the [canister migration guide](canister-migration.md#migrating-with-the-canister-id) for the complete step-by-step workflow, including NNS controller cleanup and interruption recovery. +- **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. @@ -154,7 +154,7 @@ 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#example-transferring-state-between-canisters): Download/upload workflow for transferring state to a canister on a different subnet +- [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 From fae7ad6ff4627343652ae4640c3039e76ccdbe50 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:17:40 +0200 Subject: [PATCH 05/10] docs(canister-management): remove undocumented protocol reference, tighten copy - Remove load_canister_snapshot parenthetical from snapshots.md and canister-migration.md: the protocol-level cross-canister same-subnet restore is real (per IC interface spec) but the icp-cli doesn't expose it and we have no guide for it; replace with the accurate icp-cli-level constraint ("snapshot must exist on target before restore") - Trim subnet-selection troubleshooting bullet to one sentence + link - Snapshots Next steps: "to another canister" instead of "to a canister on a different subnet" (download/upload works for any target canister) --- docs/guides/canister-management/canister-migration.md | 2 +- docs/guides/canister-management/snapshots.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index a11122e..8e6335f 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -53,7 +53,7 @@ Note the canister ID printed in the output. Add `--quiet` to print only the ID, ### 2. Transfer state via snapshots -The snapshot download/upload workflow is required for cross-subnet transfers because the direct restore (`load_canister_snapshot`) only works within the same subnet. See [Canister snapshots](snapshots.md#downloading-and-uploading-snapshots) for full details on resuming interrupted transfers. +See [Canister snapshots](snapshots.md#downloading-and-uploading-snapshots) for full details on resuming interrupted transfers. ```bash # Stop and snapshot the source canister diff --git a/docs/guides/canister-management/snapshots.md b/docs/guides/canister-management/snapshots.md index 3474dd4..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. From 4f8def3139c68a3736db3f666747632aa1abd0aa Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:25:26 +0200 Subject: [PATCH 06/10] docs(canister-management): fix danger callout accuracy in migration guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two corrections in the "Choosing your approach" section: - Replace "load-bearing" with "whether the canister ID can change" and "The canister ID cannot change when" — avoids informal construction metaphor, says the same thing in plain ICP terms - Rewrite danger callout: "no recovery path" was wrong. Snapshot transfer retains the source canister, so the original tECDSA/vetKeys remain accessible through it until the source is deleted. Callout now explains the recovery window (stop target, switch back, do full migration) and names the actual point of no return (source canister deleted). --- docs/guides/canister-management/canister-migration.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index 8e6335f..9239206 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -18,7 +18,7 @@ Consider migrating a canister when: ## Choosing your approach -Your options depend on whether the canister ID is load-bearing: +Your options depend on whether the canister ID can change: | Approach | State | Canister ID | Source canister | Complexity | |---|---|---|---|---| @@ -27,14 +27,16 @@ Your options depend on whether the canister ID is load-bearing: **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. A canister ID is load-bearing in these situations: +**Full migration** is required when the canister ID must be preserved. The canister ID cannot 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 -Choosing snapshot transfer when the canister ID is load-bearing causes permanent, irreversible loss. Any threshold signature keys (tECDSA / tSchnorr) and the Bitcoin or Ethereum addresses derived from them are gone. Any data encrypted under a vetKey becomes permanently inaccessible. There is no recovery path. Verify whether your canister derives threshold signatures or vetKeys before choosing an approach. +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 — 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 From 970512784f9a91def801f10fb89448d7ee0ebcd8 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:26:40 +0200 Subject: [PATCH 07/10] fix: remove em-dashes from canister-migration.md --- docs/guides/canister-management/canister-migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index 9239206..49eadf0 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -36,7 +36,7 @@ Your options depend on whether the canister ID can change: :::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 — before deleting the source canister. Once the source is deleted, those keys and any assets or data tied to them are permanently gone. +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 @@ -184,7 +184,7 @@ Snapshots capture the Wasm module and memory, but not canister settings. Control ```bash icp canister settings show my-canister -e ic -# Apply non-default settings to the target (do not copy controllers — they are restored automatically) +# 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 \ From 0915970a312ae1c7df5917b6b4523e12054722cb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:28:10 +0200 Subject: [PATCH 08/10] docs: use 'must not change' over 'cannot change' in migration guide --- docs/guides/canister-management/canister-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index 49eadf0..9c9eba7 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -27,7 +27,7 @@ Your options depend on whether the canister ID can change: **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 cannot change when: +**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. From 7e6d3b31adf9ffe1b943df61b60e9fdf586f6c9b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:43:13 +0200 Subject: [PATCH 09/10] docs(canister-management): reorder sidebar and slim lifecycle migration section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar reorder (core ops → build quality → deployment topology): - cycles-management: 7 → 4 (fundamental, not advanced) - snapshots: 5 unchanged (stays after cycles, supports upgrade safety) - optimization: 4 → 6 - reproducible-builds: 6 → 7 - large-wasm: 10 → 8 (closes build quality cluster) - subnet-selection: 8 → 9 - canister-migration: 9 → 10 lifecycle.mdx migration section: replace 22-line duplicate (table + bullets + GitHub raw link) with a two-sentence pointer to canister-migration.md; add subnet selection and canister migration to Next steps --- .../canister-management/canister-migration.md | 2 +- .../canister-management/cycles-management.mdx | 2 +- docs/guides/canister-management/large-wasm.md | 2 +- docs/guides/canister-management/lifecycle.mdx | 24 +++---------------- .../canister-management/optimization.md | 2 +- .../reproducible-builds.md | 2 +- .../canister-management/subnet-selection.md | 2 +- 7 files changed, 9 insertions(+), 27 deletions(-) diff --git a/docs/guides/canister-management/canister-migration.md b/docs/guides/canister-management/canister-migration.md index 9c9eba7..4f53bec 100644 --- a/docs/guides/canister-management/canister-migration.md +++ b/docs/guides/canister-management/canister-migration.md @@ -2,7 +2,7 @@ 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: 9 + 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. 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 e3e1092..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: 10 + 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..874998e 100644 --- a/docs/guides/canister-management/lifecycle.mdx +++ b/docs/guides/canister-management/lifecycle.mdx @@ -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/subnet-selection.md b/docs/guides/canister-management/subnet-selection.md index e0ceb62..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. From 3add58c56c4f95d7c2f676a08c62b06a4cf0d1b0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 13:48:19 +0200 Subject: [PATCH 10/10] fix(lifecycle): link 'principal' to concepts/principals.md --- docs/guides/canister-management/lifecycle.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/canister-management/lifecycle.mdx b/docs/guides/canister-management/lifecycle.mdx index 874998e..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