mcpField
I
icp-cli

icp-cli

github.com/dfinity云与 DevOps39377 次安装今天更新
代码仓库

关于 icp-cli

icp-cli 技能介绍用于构建和部署 Internet Computer 应用的命令行工具,面向使用 icp.yaml 管理项目的开发者。它说明配方、环境和部署流程,并列出配置及迁移中的常见陷阱。

主要功能

  1. 01构建并部署应用
  2. 02通过 YAML 配置项目
  3. 03使用配方复用构建模板
  4. 04以环境区分部署目标
  5. 05身份、网络与 canister 管理

适用场景

  1. 01部署 Rust 或 Motoko canister
  2. 02配置本地网络进行部署
  3. 03将项目从 dfx 迁移到 icp

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dfinity/icskills --skill icp-cli

安装后在 Claude Code 中输入/icp-cli确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
binding-generation.md
bundling.md
canister-env-vars.md
dev-server.md
dfx-migration.md

详细介绍

ICP CLI

这是什么

icp 命令行工具用于在 Internet Computer 上构建和部署应用。它以 YAML 配置、用于复用构建模板的 recipe 系统,以及将部署目标与网络连接分开的环境模型,取代了旧版 dfx 工具。切勿使用 dfx——始终使用 icp。

在生成此处未明确记录的 icp 命令前,先运行 icp --help 或 icp <subcommand> --help,确认命令及其标志确实存在。不要根据 dfx 对应项推断标志——两个 CLI 的标志并不兼容。

安装

npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm

使用官方 recipe(@dfinity/rust、@dfinity/motoko、@dfinity/static-site、@dfinity/asset-canister)时需要 ic-wasm——这些 recipe 依赖它进行优化和元数据嵌入。需要 Node.js >= 22。也可通过 Homebrew 和 shell 脚本安装程序获取——请参阅 icp-cli releases。

Linux 注意事项: 在精简安装环境中,你可能需要系统库:sudo apt-get install -y libdbus-1-3 libssl3 ca-certificates(Ubuntu/Debian)或 sudo dnf install -y dbus-libs openssl ca-certificates(Fedora/RHEL)。

前置条件

  • 对于 Rust canister:rustup target add wasm32-unknown-unknown
  • 对于 Motoko canister:npm i -g ic-mops,并在项目根目录下准备一个 mops.toml,其中包含 Motoko 编译器版本和 [canisters] 条目:
    [toolchain]
    moc = "1.9.0"
    
    [canisters.backend]
    main = "src/backend/main.mo"
    
    @dfinity/motoko@v5+ recipe 通过 mops build <canister-name> 进行编译。icp.yaml 中的 canister 名称必须与 [canisters] 中的键完全一致——缺少或不匹配的键会导致 mops build 失败,并报错 No Motoko canisters found in mops.toml configuration(参见常见问题 17)。如果没有 mops.toml,recipe 会因找不到 mops 而失败。模板会自动包含 mops.toml;对于手动创建的项目,请在运行 icp build 前创建该文件。加载 mops-cli,以了解 [canisters] 配置选项、依赖管理和 mops build 的详细信息。

常见问题

  1. 使用 dfx 而非 icp。 dfx 是旧版工具。所有命令都有对应的 icp 命令——完整的命令映射请参阅 references/dfx-migration.md。切勿生成 dfx 命令或引用 dfx 文档。配置使用 icp.yaml,而不是 dfx.json——且结构也不同:canister 是对象数组,而非以键为索引的对象。

  2. 使用 --network ic 部署到主网。 icp-cli 使用环境,而不是直接指定网络。正确的标志是 -e ic(--environment ic 的简写)。

    # Wrong
    icp deploy --network ic
    # Correct
    icp deploy -e ic
    

    注意:-n / --network 直接指定网络,适用于使用 canister ID(principal)的情况。按名称引用 canister 时,请使用 -e / --environment。对于代币和 cycles 操作,请使用 -n,因为这些操作不引用项目 canister。

  3. 使用未固定版本的 recipe。 icp-cli 会拒绝未固定版本的 recipe 引用。务必指定明确的版本。官方 recipe 托管于 dfinity/icp-cli-recipes。

    # Wrong — rejected by icp-cli
    recipe:
      type: "@dfinity/rust"
    
    # Correct — pinned version
    recipe:
      type: "@dfinity/[email protected]"
    
  4. 已有 recipe 时仍手动编写构建步骤。 官方 recipe 支持 Rust、Motoko 和 asset canister 构建。使用 recipe: { type: "@dfinity/[email protected]" },而不是在 build.steps 中编写 shell 命令——Rust recipe 默认将 Cargo 的 package 设为 canister 名称,因此两者一致时无需配置(参见常见问题 21)。

  5. 未将 .icp/data/ 提交到版本控制。 主网 canister ID 存储在 .icp/data/mappings/<environment>.ids.json 中。丢失此文件就意味着丢失 canister 名称与链上 ID 之间的映射。务必提交 .icp/data/——切勿删除。将 .icp/cache/ 添加到 .gitignore(该目录是临时的,会自动重新生成)。如果某些环境使用的连接网络经常重置,可以将这些环境对应的映射文件单独添加到 .gitignore。切勿将整个 .icp 或 .icp/data 目录添加到 gitignore。

  6. 使用 icp identity use 而不是 icp identity default。 dfx 命令 dfx identity use <name> 在 icp-cli 中变为 icp identity default <name>(设置器)。不带参数的 icp identity default 是获取器,会打印当前默认身份,相当于 dfx identity whoami。命令 icp identity use 不存在。同样,dfx identity get-principal 变为 icp identity principal,dfx identity remove 变为 icp identity delete。

  7. 混淆网络和环境。 网络是连接端点(URL)。环境由网络 + canisters + 设置组成。部署目标是环境(-e),而不是网络。多个环境可以使用不同设置指向同一个网络(例如,staging 和 production 都使用 ic)。

  8. 将 networks 或 environments 写成 YAML 映射,而不是数组。 在 icp.yaml 中,networks 和 environments 都是对象数组,而不是映射:

    # Wrong — map syntax
    networks:
      local:
        mode: managed
    environments:
      staging:
        network: ic
    
    # Correct — array syntax
    networks:
      - name: local
        mode: managed
    environments:
      - name: staging
        network: ic
        canisters: [backend, frontend]
    
  9. 忘记本地网络是项目专属的。 与 dfx 运行一个共享的全局网络不同,icp-cli 会为每个项目运行一个本地网络。在本地部署之前,必须在项目目录中运行 icp network start -d。本地网络会自动启动系统 canisters,并为账户预置 ICP 和 cycles。完成后请停止它:

    icp network start -d  # start background network
    icp deploy            # build + deploy + sync
    icp network stop      # stop when done
    
  10. 未为前端 canister 指定构建命令。 dfx 会自动为资产 canister 运行 npm run build。icp-cli 要求在 recipe 配置中显式指定构建命令:

    canisters:
      - name: frontend
        recipe:
          type: "@dfinity/[email protected]"   # 推荐的前端 recipe(certified-assets)
          configuration:
            dir: dist
            build:
              - npm install
              - npm run build
    
  11. 误以为 output_env_file 或 .env 中会包含 canister ID。 dfx 会通过 output_env_file 将 canister ID 写入 .env 文件(CANISTER_ID_BACKEND=...)。icp-cli 不会生成 .env 文件。相反,icp deploy 会将 canister ID 作为环境变量(PUBLIC_CANISTER_ID:<name>)直接注入 canisters。前端会从前端 canister(static-site 或旧版 asset canister)设置的 ic_env cookie 中读取这些变量。请从配置中移除 output_env_file,并删除任何从 .env 读取 CANISTER_ID_* 的代码——前端使用 ic_env cookie,而 canister 代码会在运行时读取相同的变量(参见下文“Canister 环境变量”和陷阱 22)。

  12. 误以为 TypeScript 绑定可以通过 dfx generate 生成。 icp-cli 没有与 dfx generate 等效的命令。使用 @icp-sdk/bindgen(>= 0.4.0),并将 @icp-sdk/core 固定为 ^6(SDK 的其他 peer 依赖均为 ^6,因此不同的主版本会导致安装失败并出现 ERESOLVE),以便在构建时从 .did 文件生成 TypeScript 绑定。使用 outDir: "./src/bindings",确保导入路径简洁(例如 ./bindings/backend)。.did 文件必须存在于磁盘上——可以将其提交到仓库,或先用 icp build 生成(未指定 candid 时,recipes 会自动生成该文件)。完整的 Vite 插件配置请参见 references/binding-generation.md。

  13. 将 @dfinity/agent 的 actor 设置方式移植到 @icp-sdk/bindgen。 旧的 @dfinity/agent 模式是 createActor(canisterId, { agent }),搭配 new HttpAgent({ identity, host })。@icp-sdk/bindgen 的模式是 createActor(canisterId, { agentOptions: { identity, rootKey } })——绑定会在内部创建 agent,浏览器代码中不设置 host。预先构建的 { agent } 仍然可用,但 new HttpAgent() 已弃用。正确的模式以及必须设置 host 的情况,请参见 references/binding-generation.md。

  14. 混用不同配置风格中的 canister 级字段。 使用配方时,唯一有效的 canister 级字段是 name、recipe、sync、settings 和 init_args。candid、build 或 wasm 等字段不能与配方一起放在 canister 级别——配方专用选项应放在 recipe.configuration 中。使用裸 build(不使用配方)时,有效的 canister 级字段是 name、build、sync、settings 和 init_args。不存在 init_arg_file 字段——请改用 init_args.path(例如,init_args: { path: ./args.bin, format: bin })。权威字段参考请参阅 icp-cli 配置参考。

    # 错误 — 使用配方时,candid 不是 canister 级字段
    canisters:
      - name: backend
        candid: backend/backend.did
        recipe:
          type: "@dfinity/[email protected]"
    
    # 正确 — candid 应放在 recipe.configuration 中
    canisters:
      - name: backend
        recipe:
          type: "@dfinity/[email protected]"
          configuration:
            candid: backend/backend.did
    
  15. 将 mops.toml 放在 mops 无法找到的位置。 mops 会从构建工作目录开始向上搜索。mops.toml 的放置位置取决于 canister 的定义方式:

    • 内联 canister(直接在 icp.yaml 中定义):构建工作目录是项目根目录。请将 mops.toml 放在项目根目录,与 icp.yaml 同级。放在 src/backend/ 中的 mops.toml 将无法被找到。
    • 基于路径的 canister(通过 canisters/* 或 ./my-canister 引用,每个 canister 都有自己的 canister.yaml):构建工作目录是 canister 目录。对于每个 canister 专用的依赖项和编译器版本,请将 mops.toml 放在各自的 canister 目录中;也可以省略该文件,回退使用父目录中的共享 mops.toml。

    找不到 mops.toml 时,mops build 会因无法定位项目配置而失败。如果 mops.toml 存在,但缺少匹配的 [canisters.<name>] 条目,请参阅陷阱 17。

  16. 误解配方中的 Candid 文件生成方式。 绑定生成工具(例如,@icp-sdk/bindgen)需要磁盘上已知路径处存在 .did 文件。配置方式取决于配方:

    Rust — 在 icp.yaml 中,candid 应放在 recipe.configuration 内:

    • 如果已指定:该文件必须已存在。配方会直接使用它,不会生成文件。
    • 如果未指定:配方会通过 candid-extractor 将 .did 自动生成到构建缓存中(项目内没有可预测的路径)。

    若要生成并提交该文件,然后在 recipe.configuration 中添加 candid: backend/backend.did:

    cargo install candid-extractor  # 一次性设置
    icp build backend
    candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did
    

    Motoko(v5 配方) — mops build 会自动将 .did 生成到 .mops/.build/<name>.did。

    • 不需要生成绑定 — 无需操作。.mops/.build/ 中生成的 .did 已足够;请勿提交该文件。
    • 需要生成绑定 — 使用 mops generate candid 在稳定路径生成经过整理的 .did:
      mops generate candid backend
      
      该命令会直接从 Motoko 源代码提取 Candid 接口,无需编译 WASM。如果在 mops.toml 中设置了 [canisters.backend].candid,命令会覆盖该文件;否则会在 main 文件旁写入 <name>.did(例如,main = "src/backend/main.mo" → src/backend/backend.did),并在 mops.toml 中记录其路径。将绑定工具的配置(例如,@icp-sdk/bindgen 的 didFile)指向该 .did。每次接口变更后都要重新运行,并一并提交 .did 和 mops.toml。详细信息请加载 mops-cli。(旧的两步操作 mops build backend + cp .mops/.build/backend.did … 仍然可用,但它会为了提取接口而构建完整的 WASM。)
  17. mops.toml 中缺少或不匹配的 [canisters] 键。@dfinity/motoko@v5+ 配方会调用 mops build <canister-name>,其中名称取自 icp.yaml 中的 name 字段。mops build 要求 mops.toml 中存在匹配的 [canisters.<name>] 条目。如果该条目缺失,或者键名不完全匹配(包括大小写),构建就会失败,并显示:

    No Motoko canisters found in mops.toml configuration
    

    添加匹配的条目——键名必须与 icp.yaml 中的 name: 值相同:

    [canisters.backend]
    main = "src/backend/main.mo"
    
  18. **启动本地网络时,端口 8000 已被占用。**有两种情况:

    **情况 A——另一个 icp-cli 项目占用了该端口。**使用 --project-root-override(所有命令都可用的全局标志)停止该项目的网络:

    icp network stop --project-root-override /path/to/other-project
    

    如果要同时运行两个网络,而不是停止其中一个(例如,使用并行 Git 工作树),请设置 gateway.port: 0,让每个网络都使用空闲端口。请参阅「工作原理」中的「并行 Agent 和工作树」。

    **情况 B——非 icp 服务占用了该端口。**在 icp.yaml 中配置备用端口,并通过 icp network status --json 动态读取实际 URL,而不是将 localhost:8000 硬编码:

    networks:
      - name: local
        mode: managed
        gateway:
          port: 8001
    
    icp network status --json  # 返回网关 URL、副本 URL 等
    
  19. **在 CI 中,icp new 未使用 --silent 时会卡住。**如果没有 --define 标志,icp new 会启动交互式提示,在非交互式环境中无限期阻塞。以脚本方式使用时,始终传入 --subfolder、--define 和 --silent:

    icp new my-project --subfolder rust --define project_name=my-project --silent
    
  20. **在主网上使用匿名身份。**本地网络启动时会为所有托管身份(包括默认的匿名身份)注入 ICP 和 cycles,因此本地开发开箱即用,无需设置身份或 cycles。在主网上则不适用,且绝不应使用匿名身份:任何人都共享该身份,这意味着发送给它的 ICP 可被公众访问,而以该身份部署的 canister 不受控制。

    部署到主网前,请切换到具名身份:

    icp identity list                                          # 检查可用身份(使用 `identity`,不要使用 `identities`)
    icp identity default my-identity                           # 切换到已有身份
    # or: icp identity new my-identity && icp identity default my-identity
    

    然后确认该身份有资金——新身份需要先获得 ICP 或 cycles,才能继续:

    icp token balance -n ic    # 检查主网上的 ICP 余额
    icp cycles balance -n ic   # 检查主网上的 cycles 余额
    icp identity account-id    # 获取账户 ID,以便在需要时充值
    

    如果有银行卡但没有 ICP,可使用 icp cycles buy --amount <usd> -n ic(或使用 --cycles <n> 指定部署所需的数量;估算方法请参阅下文「为主网身份注资」),通过 CyclePay 的链上网关为身份本身购买 cycles:Agent 创建订单,人类通过打印出的链接付款,cycles 随后会存入该身份的 cycles 账本账户,icp deploy -e ic 会从该账户扣款。加载 buy-cycles 技能,了解操作流程、Agent 标志(--yes --no-open --json)和订单状态。

  21. 在 Rust 配方中对 package 进行过度指定(或错误地省略)。 从 @dfinity/[email protected] 开始,package 配置为可选,默认使用 icp.yaml 中的 canister name。按照惯例,应使 canister name 与 Cargo.toml 中的 [package] name 相同,并完全省略 package:

    # 首选 — 名称与 Cargo 包匹配,因此无需 package 配置
    canisters:
      - name: backend        # 与 Cargo.toml 中的 [package] name = "backend" 匹配
        recipe:
          type: "@dfinity/[email protected]"
    
    # 仅当名称不同时才需要(例如,工作区 crate)
    canisters:
      - name: backend
        recipe:
          type: "@dfinity/[email protected]"
          configuration:
            package: my-project-backend   # Cargo.toml 中实际的 [package] name
    

    如果名称不同却省略 package,cargo build 会因找不到包而失败 — 请将其设为完全一致的 [package] name。在 v3.3.0 之前的配方版本中,所有情况下都必须指定 package。

  22. 通过 setter 方法或部署脚本手动连接 canister ID。 由部署后脚本调用的仅限控制器的 setter(setBridge(principal))——或在 settings.environment_variables 或 init 参数中硬编码同级 canister ID——都没有必要:icp deploy 会将每个 canister 的 ID 注入每个 canister 的设置中,格式为 PUBLIC_CANISTER_ID:<canister-name>;canister 代码可在运行时读取该值,并获取对应环境中的正确值(参见 Canister 环境变量)。Setter 连接也更脆弱:--mode reinstall 会静默清除存储的指针,而自动变量会在每次部署时重新写入。

工作原理

创建项目

icp new 使用模板搭建项目。对于非交互式使用,请传入 --subfolder、--define 和 --silent:

icp new my-project --subfolder rust --define project_name=my-project --silent

可用模板和选项:dfinity/icp-cli-templates。

构建 → 部署 → 同步
Source Code → [Build] → WASM → [Deploy] → Running Canister → [Sync] → Configured State

icp deploy 会依次运行全部三个阶段:

  1. 构建 — 将 canister 编译为 WASM(通过配方或显式构建步骤)
  2. 部署 — 创建 canister(如果是新的)、应用设置并安装 WASM
  3. 同步 — 通过 script 或 plugin 步骤执行部署后操作(例如上传资源)。CLI 不内置资源上传功能:前端配方会提供一个 plugin 同步步骤,用于上传 dir 的内容 — 推荐使用 @dfinity/[email protected](认证资源)或旧版 @dfinity/[email protected]。旧版内置的 type: assets 步骤已在 icp-cli 0.3.0 中移除 — 请参阅 static-site skill。

单独运行各阶段可获得更多控制权:

icp build                     # Build only
icp deploy                    # Full pipeline (build + deploy + sync)
icp sync my-canister          # Sync only (e.g., re-upload assets)
环境和网络

始终提供两个隐式环境:

环境网络用途
locallocal(托管,localhost:8000)本地开发
icic(已连接,https://icp-api.io)主网生产环境

ic 网络受到保护,无法覆盖。

自定义环境可在同一网络上启用多个部署目标:

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
并行 Agent 和 worktree
隔离网关端口

本地网络按项目隔离,由项目根目录标识(陷阱 9)。同一仓库的不同 Git 工作树拥有不同的项目根目录,因此每个工作树都可以运行独立的本地网络。这样,多个 Agent 或分支就能并行构建和部署,互不干扰。唯一的障碍是网关端口:每个工作树的默认端口都是 8000,所以第二次运行 icp network start 会因端口冲突而失败。

将受管网络的网关端口设为 0,让操作系统为每个工作树分配一个空闲的临时端口:

networks:
  - name: local
    mode: managed
    gateway:
      port: 0   # 0 = OS picks a free port — avoids collisions across worktrees

icp network start -d 会打印所选端口(Network started on port 58157)。之后,如需在测试、脚本或其他 Agent 中获取该端口,可查询运行中的网络并读取 gateway_url:

icp network start -d
icp network status --json
# -> { "managed": true, "api_url": "http://localhost:58157/", "gateway_url": "http://localhost:58157/", ... }

icp network status --json | jq -r '.gateway_url'   # http://localhost:58157/

使用 port: 0 时,切勿将 localhost:8000 硬编码,因为每次启动时端口都会变化,因此每次都应从 icp network status --json 读取 gateway_url(或 api_url)。若要从工作树目录之外访问特定工作树的网络,请传入全局 --project-root-override <path> 标志(例如 icp network status --json --project-root-override /path/to/worktree)。

隔离全局 CLI 状态(ICP_HOME)

项目本地网络只隔离每个项目的状态。身份、设置和下载的软件包缓存都是全局的——每位 OS 用户各有一个目录,位于 ~/.local/share/icp-cli/(Linux)、~/Library/Application Support/org.dfinity.icp-cli/(macOS)或 %APPDATA%\icp-cli\data\(Windows)。其中的 identity/、settings/ 和 pkg/ 各自都由阻塞式建议锁(.lock)保护,因此,同时运行的 icp 调用若访问同一目录,会串行执行——它们会等待锁,而不会失败。多个 Agent 同时构建时,如果配方或网络启动器正在下载,就会争用软件包缓存锁。

ICP_HOME 可以覆盖该目录,让每个 Agent 都拥有自己的副本:

export ICP_HOME=/path/to/worktree/.icp-home
icp identity new dev --storage plaintext
icp deploy

注意事项(按遇到的先后顺序):

  • OS 密钥环仍然是共享的。 密钥环条目由服务 icp-cli 和身份名称确定——ICP_HOME 不属于密钥。两个相互隔离的主目录若都运行 icp identity new dev(默认使用密钥环存储),就会写入同一条目并相互覆盖。传入 --storage plaintext,让密钥存储在 $ICP_HOME/identity/keys/<name>.pem 中,才能真正实现隔离。icp identity import 和 icp identity link web 也有同样的问题,因为它们的会话密钥默认也存储在密钥环中。明文密钥仅用于临时的本地和 CI 身份——切勿用于控制主网 canister 或持有资产的身份(陷阱 20)。
  • 新主目录是空的。 没有身份、设置或缓存。默认身份是匿名身份;受管本地网络会自动为其注入资金,但它无法用于主网。
  • 每个主目录都会重新下载软件包缓存。 pkg/ 包含网络启动器和所有配方,占用空间可达数百 MB。可将已有完整缓存的 pkg/ 复制到新的主目录中,或使用 ICP_CLI_NETWORK_LAUNCHER_PATH=/path/to/icp-cli-network-launcher,让所有主目录共用一个网络启动器二进制文件。
  • 该 Agent 会话中的每次 icp 调用都必须设置 ICP_HOME,而不只是第一次。若某条命令漏设该变量,就会静默回退到共享的默认主目录,并使用不同的默认身份。

只有在 Agent 确实发生争用时才采用此方案。不同工作树若共用一组身份,并且能接受缓存访问串行执行,就无需隔离。

安装模式
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)
将项目打包为 .icp 软件包(实验性)

icp project bundle 可将项目打包为独立的可部署归档(icp-cli >= 0.3.0)。这是一项实验性功能,特意不显示在帮助输出中——icp --help 和 icp project --help 都不会列出此命令,但该命令确实存在且可正常使用。不要因为帮助中未列出它就断定它不存在,也不要主动建议使用它——仅在用户明确要求打包应用或生成 .icp 软件包时使用。

icp project bundle --output my-app.icp      # gzipped tar; --output accepts any path

关于如何从软件包部署、归档包含的内容、导致打包失败的条件,以及如何将软件包上传到 Caffeine 云引擎,请参阅 references/bundling.md。

配置

Rust canister
canisters:
  - name: backend
    recipe:
      type: "@dfinity/[email protected]"
      configuration:
        candid: backend.did  # optional — if specified, file must exist (auto-generated when omitted)
        # package: my-crate  # optional — only when the Cargo [package] name differs from the canister name

按照惯例,canister 的 name 必须与 Cargo.toml 中的 [package] name 一致。如果二者一致,package 是可选的(recipe >= v3.3.0 默认将其设为 canister 名称)。仅当 Cargo package 名称不同时才显式设置 package——例如,工作区中 crate 的名称不同(参见 Pitfall 21)。

Motoko canister

v5 recipe 将编译工作交由 mops build 完成。canister 配置项(main、candid、args)从 icp.yaml 移至 mops.toml:

# icp.yaml
canisters:
  - name: backend
    recipe:
      type: "@dfinity/[email protected]"
# mops.toml
[toolchain]
moc = "1.9.0"

[canisters.backend]
main = "src/backend/main.mo"
candid = "backend.did"  # optional — auto-generated to .mops/.build/ when omitted

backend 这个 canister 名称必须在 icp.yaml 和 mops.toml 中完全一致。icp.yaml 中不需要 recipe.configuration 块。

静态网站(前端)
canisters:
  - name: frontend
    recipe:
      type: "@dfinity/[email protected]"
      configuration:
        dir: dist
        build:
          - npm install
          - npm run build

@dfinity/static-site(认证资产 canister)是推荐的前端 recipe。旧版 @dfinity/[email protected] recipe 仍受现有项目支持——两者的用法及迁移方法请参阅 static-site 技能。

对于多 canister 项目,请在同一个 canisters 数组中列出所有 canister。icp-cli 会并行构建它们。没有 dependencies 字段——请使用 Canister 环境变量进行 canister 间通信。

自定义构建步骤(不使用 recipe)

不使用 recipe 时,canister 级别仅允许 name、build、sync、settings 和 init_args 字段。没有 wasm、candid 或 metadata 字段——请改为在构建脚本中处理这些内容:

  • WASM 输出:将最终的 WASM 复制到 $ICP_WASM_OUTPUT_PATH
  • Candid 元数据:使用 ic-wasm 嵌入 candid:service 元数据
  • Candid 文件:.did 文件仅在 ic-wasm 命令中引用,不作为 YAML 字段使用
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
可用的 recipe
Recipe类型字符串必需配置可选配置
Rust@dfinity/[email protected]—package(默认为 canister 名称)、candid、locked、shrink、compress、metadata
Motoko@dfinity/[email protected]—shrink、compress、metadata
静态网站(前端)@dfinity/[email protected]dirbuild、presync、metadata
Asset(旧版前端)@dfinity/[email protected]dirbuild、version
预构建@dfinity/[email protected]pathsha256、shrink、compress、metadata

@dfinity/static-site 是托管前端的推荐方式;@dfinity/asset-canister 是旧版资产 canister(参见 static-site 技能)。

请在 dfinity/icp-cli-recipes releases 查看最新的 recipe 版本。使用 icp project show 查看 recipe 展开后的有效配置。

Canister 环境变量

在执行 icp deploy 时,icp-cli 会将环境中每个 canister 的 ID注入每个 canister 的设置,变量名为 PUBLIC_CANISTER_ID:<canister-name>(主体的文本形式)。这些是 canister 设置环境变量,可供 canister 代码在运行时读取;每次部署都会刷新各环境的变量值,因此无需任何配置,同一份代码即可在本地、预发布环境和主网上正确运行。

请使用这些变量连接 canister 间的调用,不要使用 setter 方法、初始化参数或部署后脚本(陷阱 22)。在调用时再读取这些变量:Motoko:Runtime.envVar<system>(...)(motoko-core v2.1.0+);Rust:ic_cdk::api::env_var_value(...)。代码及首次安装/重新安装时的行为,请参阅 references/canister-env-vars.md。

前端(JavaScript):前端 canister(静态站点或旧版 asset canister)会通过 ic_env cookie 暴露这些变量,并在所有 HTML 响应中设置该 cookie。使用 @icp-sdk/core 读取:

import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";

const backendId = safeGetCanisterEnv()?.["PUBLIC_CANISTER_ID:backend"];

注意事项:

  • 变量仅在部署时更新。添加 canister 后,请运行 icp deploy(不指定 canister 名称),确保每个 canister 都获取完整的 ID 集合。
  • 用户定义的变量(icp.yaml 中的 settings.environment_variables)是跨环境共享的静态 YAML 字符串:执行 create-class 安装(首次安装或使用 --mode reinstall)时会重新应用这些变量,覆盖通过 icp canister settings update --add-environment-variable 设置的值。执行此类部署后,请重新设置特定环境的值(例如生产环境的来源)。
  • replica 将环境变量的值限制为最多 128 个字符。

完整参考:icp-cli 环境变量。

Web 身份流程

有两个专门的技能涵盖 icp identity link web。请根据任务加载合适的技能——两者都会说明 stdin 中的“Press Enter”阻塞、浏览器登录步骤及各自标志的注意事项:

  • deploy-to-cloud-engine — 使用 --auth <console-origin> 将 CLI 连接到云引擎控制台,然后部署到该引擎的子网
  • agent-web-identity — 使用 --app <domain> 获取特定于应用的主体的委托,然后以用户身份调用 canister
为主网提供身份资金

新身份既没有 ICP,也没有 cycles。此时运行 icp deploy -e ic 会失败,并提示 Insufficient cycles. Requested: <n> cycles, available balance: 0 cycles.(或 cycles 账本返回 insufficient liquid cycles balance):该部署身份在主网上的 cycles 账本余额为空。CLI 不会事先估算或发出警告,因此请在部署前自行处理,而不是等失败后再处理:

  1. 统计尚待创建的 canister 数量。 icp deploy 会创建当前环境中所有在 .icp/data/mappings/<env>.ids.json 里没有 ID 的 canister(这是一个 name: id 映射;首次部署前该文件不存在,因此届时所有 canister 都会创建)。运行 icp deploy --no-create -e ic 可在不访问网络的情况下列出这些 canister:它会先构建,然后因提示 the following canisters do not exist: ... 而停止。
  2. 估算所需资金。 每次创建都会从身份余额中扣除 --cycles 指定的数量(默认 2T;后缀为 k/m/b/t),以及 cycles 账本收取的 100M 手续费,因此 N 个 canister 需要 N × (2T + 100M)。网络创建费(13 节点子网上为 500B)和安装费从每个 canister 的 2T 中扣除,而非从身份余额中扣除;请再加上约 10% 的余量,以便之后可以充值。
  3. 对比将要执行部署的身份的余额:icp cycles balance -n ic --identity "$ID"(陷阱 20;-q 会输出不带其他内容的数字)。
  4. 如果余额不足,不要猜测:告诉用户差额,并提供两种补足方式。
    • 持有 ICP(通过 icp token balance -n ic 确认余额充足):运行 icp cycles mint --cycles <gap> -n ic,只兑换所需差额(参见 cycles-management)。
    • 有银行卡但没有 ICP:运行 icp cycles buy --cycles <gap> -n ic --identity "$ID" --no-open --yes --json,以最低金额购买足以补足差额的 cycles;加载 buy-cycles 技能,了解操作流程、链接交接方式和订单状态。由用户付款;Agent 等待 icp cycles balance -n ic 达到估算值。
  5. 使用同一身份进行部署: icp deploy -e ic --identity "$ID"。

补充参考资料

完整的 CLI 和配置架构请参阅 icp-cli 文档索引。

特定主题的详细指南请按需参阅以下参考文件:

  • references/binding-generation.md — 使用 @icp-sdk/bindgen 生成 TypeScript 绑定(Vite 插件、CLI、actor 设置)
  • references/bundling.md — 完整介绍 icp project bundle:归档内容、导致打包失败的四种情况,以及如何将 .icp 包上传至 Caffeine 云引擎
  • references/canister-env-vars.md — 在 canister 代码运行时读取 PUBLIC_CANISTER_ID:<name>(Motoko/Rust 示例、延迟读取模式)
  • references/dev-server.md — 配置 Vite 开发服务器,以便在本地模拟 ic_env cookie。重要:请用 command === "serve" 条件包裹 getDevServerConfig(),确保它只在 vite dev 期间运行,而不是在 vite build 期间运行。
  • references/dfx-migration.md — 完整的 dfx → icp 迁移指南(命令映射、配置映射、身份/canister ID 迁移、前端包迁移、迁移后验证清单)

评论

支持 Markdown