Local Development
This guide covers the day-to-day development workflow with icp-cli.
The Development Cycle
Local development follows a simple loop:
Edit code → Build → Deploy → Test → RepeatStarting Your Session
Start the local network in the background:
icp network start -dVerify it’s running:
icp network pingMaking Changes
After editing your source code, deploy the changes:
icp deployThis rebuilds and redeploys all canisters. Deploy specific canisters:
icp deploy my-canisterTip: icp deploy always builds first. If you want to verify compilation before deploying, run icp build separately.
Testing Changes
Call methods on your canister:
icp canister call my-canister method_name '(arguments)'Example:
icp canister call backend get_user '("alice")'For read-only methods, use --query for faster uncertified responses:
icp canister call backend get_user '("alice")' --queryForwarding Cycles with the Proxy Canister
Managed networks include a proxy canister that forwards calls with cycles attached. This is useful for testing methods that require cycles:
icp canister call my-canister method '(args)' \ --proxy $(icp network status --json | jq -r .proxy_canister_principal) \ --cycles 1TThe proxy canister’s principal is shown in icp network status output.
Viewing Project State
List canisters configured in this environment (the local environment is the default, targeting your local network):
icp canister listView the effective project configuration:
icp project showWorking with Multiple Canisters
Deploy all canisters:
icp deployDeploy specific canisters:
icp deploy frontendicp deploy backendBuild without deploying (for verification):
icp build # Build allicp build frontend # Build specific canisterFrontend Development
Asset Canisters
Web frontends on the Internet Computer are served by asset canisters — pre-built canisters maintained by DFINITY that serve static files (HTML, JS, CSS, images) over HTTP.
The @dfinity/asset-canister recipe deploys this pre-built canister and syncs your frontend files to it:
canisters: - name: frontend recipe: type: "@dfinity/asset-canister" configuration: dir: dist # Your built frontend filesDeploy and access your frontend:
icp network start -dicp deployOpen your browser to http://<frontend-canister-id>.localhost:8000/ (the canister ID is shown in the deploy output).
Calling Backend Canisters
This section applies when your frontend needs to call backend canisters. If your frontend is purely static, you can skip this.
When a frontend calls a backend canister, it needs two things:
- The backend’s canister ID — to know which canister to call
- The network’s root key — to verify response signatures
Asset canisters solve this automatically via a cookie named ic_env:
- During
icp deploy, canister IDs are injected asPUBLIC_CANISTER_ID:*canister environment variables - The asset canister serves these variables plus the network’s root key via the
ic_envcookie - Your frontend reads the cookie using
@icp-sdk/coreto get canister IDs and root key
This works identically on local networks and mainnet — your frontend code doesn’t need to change between environments.
See Canister Discovery for implementation details.
Development Approaches
When developing a frontend that calls backend canisters, you have two options:
| Approach | Best for | Trade-offs |
|---|---|---|
| Deploy and access asset canister | Testing production-like behavior | No hot reload; must redeploy on every change |
| Use a local dev server | Fast iteration during development | Requires manual configuration |
Option 1: Deploy and access the asset canister
Deploy all canisters and access the frontend through the asset canister:
icp deployOpen http://<frontend-canister-id>.localhost:8000/
The asset canister automatically sets the ic_env cookie with canister IDs and the network’s root key.
Limitation: No hot module replacement. You must run icp deploy frontend after every frontend change.
Option 2: Use a local dev server
For hot reloading, run a dev server (Vite, webpack, etc.) that serves your frontend locally. Since your dev server isn’t the asset canister, you need to configure it to provide the ic_env cookie.
Key insight: You only need to deploy the backend canister — the frontend canister isn’t needed since your dev server serves the frontend.
icp deploy backend # Only deploy backendnpm run dev # Start your dev serverConfiguring a Dev Server
When using a dev server, configure it to:
- Fetch canister IDs and root key from the CLI at startup
- Set the
ic_envcookie with these values (mimics what asset canisters do) - Proxy
/apirequests to the target network
See the frontend-environment-variables example for a complete Vite configuration.
Workflow:
icp network start -d # Start local networkicp deploy backend # Deploy backend canisternpm run dev # Start dev server (fetches IDs automatically)Important: After icp network stop and restart, the dev server will automatically fetch new canister IDs on next startup.
Example Projects
- hello-world template — The template from
icp newshows the complete pattern for reading theic_envcookie. This is the simplest starting point. - frontend-environment-variables example — A detailed Vite setup showing dev server configuration: fetching canister IDs and root key via CLI, setting the
ic_envcookie, and using@icp-sdk/coreto parse environment variables.
Resetting State
To start fresh with a clean network:
# Stop the current networkicp network stop
# Start a new network (previous state is discarded)icp network start -dThen redeploy your canisters:
icp deployNetwork Management
Check network status:
icp network statusView network details as JSON:
icp network status --jsonExample output for a local managed network:
{ "managed": true, "api_url": "http://localhost:8000", "gateway_url": "http://localhost:8000", "candid_ui_principal": "be2us-64aaa-aaaaa-qaabq-cai", "proxy_canister_principal": "bd3sg-teaaa-aaaaa-qaaba-cai", "root_key": "308182..."}| Field | Description |
|---|---|
managed | Whether icp-cli controls this network’s lifecycle |
api_url | Endpoint for canister calls |
gateway_url | Endpoint for browser access to canisters |
candid_ui_principal | Candid UI canister for testing (managed networks only) |
proxy_canister_principal | Proxy canister for forwarding calls with cycles (managed networks only) |
root_key | Network’s root key for verifying responses |
For connected networks (like ic), candid_ui_principal and proxy_canister_principal are omitted.
Stop the network when done:
icp network stopTroubleshooting
Build fails with “command not found”
A required tool is missing. See the Installation Guide for:
- Rust toolchain — If error mentions
cargoorrustc - Motoko toolchain — If error mentions
mocormops - ic-wasm — If error mentions
ic-wasm
Network connection fails
Check if the network is running:
icp network pingIf not responding, restart:
icp network stopicp network start -dDeployment fails
- Verify the build succeeded:
icp build - Check network health:
icp network ping
Frontend can’t find canister IDs
If using a dev server, ensure you’ve deployed the backend before starting:
icp deploy backendnpm run dev # Start after deployIf accessing the asset canister directly, check that you’re using the correct URL format: http://<canister-id>.localhost:8000/
Next Steps
- Canister Discovery — How canisters find each other
- Deploying to Mainnet — Go live with your canisters