icp-cli
ICP CLI
What This Is
The icp command-line tool builds and deploys applications on the Internet Computer. It replaces the legacy dfx tool with YAML configuration, a recipe system for reusable build templates, and an environment model that separates deployment targets from network connections. Never use dfx — always use icp.
Installation
Recommended (npm) — requires Node.js >= 22:
npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm
ic-wasm is required when using official recipes (@dfinity/rust, @dfinity/motoko, @dfinity/asset-canister) — they depend on it for optimization and metadata embedding.
Alternative methods:
# Homebrew (macOS/Linux)
brew install icp-cli
brew install ic-wasm
# Shell script (macOS/Linux/WSL)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dfinity/icp-cli/releases/latest/download/icp-cli-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dfinity/ic-wasm/releases/latest/download/ic-wasm-installer.sh | sh
Verify:
icp --version
ic-wasm --version
Linux note: On minimal installs, you may need system libraries: sudo apt-get install -y libdbus-1-3 libssl3 ca-certificates (Ubuntu/Debian) or sudo dnf install -y dbus-libs openssl ca-certificates (Fedora/RHEL).
Prerequisites
- For Rust canisters:
rustup target add wasm32-unknown-unknown - For Motoko canisters:
npm i -g ic-mopsandmocversion defined inmops.toml(templates include this; for manual projects add[toolchain]withmoc = "<version>")
Common Pitfalls
-
Using
dfxinstead oficp. Thedfxtool is legacy. All commands haveicpequivalents — seereferences/dfx-migration.mdfor the full command mapping. Never generatedfxcommands or referencedfxdocumentation. Configuration usesicp.yaml, notdfx.json— and the structure differs: canisters are an array of objects, not a keyed object. -
Using
--network icto deploy to mainnet. icp-cli uses environments, not direct network targeting. The correct flag is-e ic(short for--environment ic).# Wrong icp deploy --network ic # Correct icp deploy -e icNote:
-n/--networktargets a network directly and works with canister IDs (principals). Use-e/--environmentwhen referencing canisters by name. For token and cycles operations, use-nsince they don't reference project canisters. -
Using a recipe without a version pin. icp-cli rejects unpinned recipe references. Always include an explicit version. Official recipes are hosted at dfinity/icp-cli-recipes.
# Wrong — rejected by icp-cli recipe: type: "@dfinity/rust" # Correct — pinned version recipe: type: "@dfinity/rust@v3.2.0" -
Writing manual build steps when a recipe exists. Official recipes handle Rust, Motoko, and asset canister builds. Use them instead of writing shell commands:
# Unnecessary — use a recipe instead build: steps: - type: script commands: - cargo build --target wasm32-unknown-unknown --release - cp target/.../backend.wasm "$ICP_WASM_OUTPUT_PATH" # Preferred recipe: type: "@dfinity/rust@v3.2.0" configuration: package: backend -
Not committing
.icp/data/to version control. Mainnet canister IDs are stored in.icp/data/mappings/<environment>.ids.json. Losing this file means losing the mapping between canister names and on-chain IDs. Always commit.icp/data/— never delete it. Add.icp/cache/to.gitignore(it is ephemeral and rebuilt automatically). -
Using
icp identity useinstead oficp identity default. The dfx commanddfx identity usebecameicp identity default. Similarly,dfx identity get-principalbecameicp identity principal, anddfx identity removebecameicp identity delete. -
Confusing networks and environments. A network is a connection endpoint (URL). An environment combines a network + canisters + settings. You deploy to environments (
-e), not networks. Multiple environments can target the same network with different settings (e.g., staging and production both onic). -
Forgetting that local networks are project-local. Unlike dfx which runs one shared global network, icp-cli runs a local network per project. You must run
icp network start -din your project directory before deploying locally. The local network auto-starts with system canisters and seeds accounts with ICP and cycles. -
Not specifying build commands for asset canisters. dfx automatically runs
npm run buildfor asset canisters. icp-cli requires explicit build commands in the recipe configuration:canisters: - name: frontend recipe: type: "@dfinity/asset-canister@v2.1.0" configuration: dir: dist build: - npm install - npm run build -
Expecting
output_env_fileor.envwith canister IDs. dfx writes canister IDs to a.envfile (CANISTER_ID_BACKEND=...) viaoutput_env_file. icp-cli does not generate.envfiles. Instead, it injects canister IDs as environment variables (PUBLIC_CANISTER_ID:<name>) directly into canisters duringicp deploy. Frontends read these from theic_envcookie set by the asset canister. Removeoutput_env_filefrom your config and any code that readsCANISTER_ID_*from.env— use theic_envcookie instead (see Canister Environment Variables below). -
Expecting
dfx generatefor TypeScript bindings. icp-cli does not have adfx generateequivalent. Use@icp-sdk/bindgen(a Vite plugin) to generate TypeScript bindings from.didfiles at build time. The.didfile must exist on disk — either commit it to the repo, or generate it withicp buildfirst (recipes auto-generate it whencandidis not specified). Seereferences/binding-generation.mdfor setup details. -
Misunderstanding Candid file generation with recipes. When using the Rust or Motoko recipe:
- If
candidis specified: the file must already exist (checked in or manually created). The recipe uses it as-is and does not generate one. - If
candidis omitted: the recipe auto-generates the.didfile from the compiled WASM (viacandid-extractorfor Rust,mocfor Motoko). The generated file is placed in the build cache, not at a predictable project path.
For projects that need a
.didfile on disk (e.g., for@icp-sdk/bindgen), the recommended pattern is: generate the.didfile once, commit it, and specifycandidin the recipe config. To generate it manually:Rust — build the WASM first, then extract the Candid interface:
cargo install candid-extractor # one-time setup icp build backend candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.didMotoko — use
mocdirectly with the--idlflag:$(mops toolchain bin moc) --idl $(mops sources) -o backend/backend.did backend/app.mo - If
How It Works
Project Creation
icp new scaffolds projects from templates. Without flags, an interactive prompt launches. For scripted or non-interactive use, pass --subfolder and --define flags directly. Available templates and options: dfinity/icp-cli-templates.
Build → Deploy → Sync
Source Code → [Build] → WASM → [Deploy] → Running Canister → [Sync] → Configured State
icp deploy runs all three phases in sequence:
- Build — Compile canisters to WASM (via recipes or explicit build steps)
- Deploy — Create canisters (if new), apply settings, install WASM
- Sync — Post-deployment operations (e.g., upload assets to asset canisters)
Run phases separately for more control:
icp build # Build only
icp deploy # Full pipeline (build + deploy + sync)
icp sync my-canister # Sync only (e.g., re-upload assets)
Environments and Networks
Two implicit environments are always available:
| Environment | Network | Purpose |
|---|---|---|
local |
local (managed, localhost:8000) |
Local development |
ic |
ic (connected, https://icp-api.io) |
Mainnet production |
The ic network is protected and cannot be overridden.
Custom environments enable multiple deployment targets on the same network:
environments:
- name: staging
network: ic
canisters: [frontend, backend]
settings:
backend:
compute_allocation: 5
- name: production
network: ic
canisters: [frontend, backend]
settings:
backend:
compute_allocation: 20
freezing_threshold: 7776000
Install Modes
icp deploy # Auto: install new, upgrade existing (default)
icp deploy --mode upgrade # Preserve state, run upgrade hooks
icp deploy --mode reinstall # Clear all state (dangerous)
Configuration
Rust canister
canisters:
- name: backend
recipe:
type: "@dfinity/rust@v3.2.0"
configuration:
package: backend
candid: backend.did # optional — if specified, file must exist (auto-generated when omitted)
Motoko canister
canisters:
- name: backend
recipe:
type: "@dfinity/motoko@v4.1.0"
configuration:
main: src/backend/main.mo
candid: backend.did # optional — if specified, file must exist (auto-generated when omitted)
Asset canister (frontend)
canisters:
- name: frontend
recipe:
type: "@dfinity/asset-canister@v2.1.0"
configuration:
dir: dist
build:
- npm install
- npm run build
For multi-canister projects, list all canisters in the same canisters array. icp-cli builds them in parallel. There is no dependencies field — use Canister Environment Variables for inter-canister communication.
Custom build steps (no recipe)
When not using a recipe, only name, build, sync, settings, and init_args are valid canister-level fields. There are no wasm, candid, or metadata fields — handle these in the build script instead:
- WASM output: copy the final WASM to
$ICP_WASM_OUTPUT_PATH - Candid metadata: use
ic-wasmto embedcandid:servicemetadata - Candid file: the
.didfile is referenced only in theic-wasmcommand, not as a YAML field
canisters:
- name: backend
build:
steps:
- type: script
commands:
- cargo build --target wasm32-unknown-unknown --release
- cp target/wasm32-unknown-unknown/release/backend.wasm "$ICP_WASM_OUTPUT_PATH"
- ic-wasm "$ICP_WASM_OUTPUT_PATH" -o "$ICP_WASM_OUTPUT_PATH" metadata candid:service -f backend/backend.did -v public --keep-name-section
Available recipes
| Recipe | Purpose |
|---|---|
@dfinity/rust |
Rust canisters with Cargo |
@dfinity/motoko |
Motoko canisters |
@dfinity/asset-canister |
Asset canisters for static files |
@dfinity/prebuilt |
Pre-compiled WASM files |
Use icp project show to see the effective configuration after recipe expansion.
Canister Environment Variables
icp-cli automatically injects all canister IDs as environment variables during icp deploy. Variables are formatted as PUBLIC_CANISTER_ID:<canister-name> and injected into every canister in the environment.
Frontend → Backend (reading canister IDs in JavaScript):
Asset canisters expose injected variables through a cookie named ic_env, set on all HTML responses. Use @icp-sdk/core to read it:
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";
const canisterEnv = safeGetCanisterEnv();
const backendId = canisterEnv?.["PUBLIC_CANISTER_ID:backend"];
Backend → Backend (reading canister IDs in canister code):
- Rust:
ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:other_canister") - Motoko (motoko-core v2.1.0+):
import Runtime "mo:core/Runtime"; let otherId = Runtime.envVar("PUBLIC_CANISTER_ID:other_canister");
Note: variables are only updated for canisters being deployed. When adding a new canister, run icp deploy (without specifying a canister name) to update all canisters with the complete ID set.
Additional References
For detailed guides on specific topics, consult these reference files when needed:
references/binding-generation.md— TypeScript binding generation with@icp-sdk/bindgen(Vite plugin, CLI, actor setup)references/dev-server.md— Vite dev server configuration to simulate theic_envcookie locallyreferences/dfx-migration.md— Complete dfx → icp migration guide (command mapping, config mapping, identity/canister ID migration, frontend package migration, post-migration verification checklist)