diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index c0889ecf..228785de 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -245,7 +245,7 @@ This project uses Snafu for error handling. - Every new *primary erroring action* gets its own error variant. There is no `MyError::Io { source: io::Error }`, instead (hypothetically) `OpenSocket` and `WriteSocket` should be separate. `snafu(context(false))` is not permitted. `snafu(transparent)` should *only* be used for source error types defined elsewhere in this repo, *not* for foreign error types. - Every error regarding a file in some way (processing, creating, etc.) should contain the file path of the error. It is okay to add 'dummy' file path parameters only used in error handling routes. For 'basic' file ops and JSON/YML loading use the functions in `icp::fs`, whose errors include the file path and can be made `snafu(transparent)`. -## Examples +## Examples and Templates The `examples/` directory contains working project templates demonstrating: @@ -293,3 +293,19 @@ Before finalizing recipe-related changes, ask the user: - What branch or version to compare with Then verify documentation matches the recipe templates from the specified source. + +### Dependency Versions in Examples/Templates + +When creating or updating examples and templates: + +1. **Always use the latest versions** of dependencies like `candid`, `ic-cdk`, `ic-cdk-macros`, etc. Use `cargo search --limit 1` to check the current latest version on crates.io before specifying dependency versions. + +2. **When updating a single example**, ask the developer whether other examples should also be updated to maintain consistency across all examples. + +3. **Schema references** in YAML files (`$schema=`) should point to the latest stable release tag (e.g., `v0.1.0`), not beta versions. + +4. **Related repository**: The `icp-cli-templates` repository (https://github.com/dfinity/icp-cli-templates) contains cargo-generate templates that should follow the same versioning guidelines. + +5. **Motoko imports**: When writing Motoko code, prefer `mo:core` over `mo:base`. See https://docs.internetcomputer.org/motoko/core for documentation. + +6. **Gitignore for ICP projects**: Only add `.icp/cache/` to `.gitignore`, NOT the entire `.icp/` directory. The `.icp/data/` directory contains mainnet canister ID mappings that should be version controlled to preserve deployment information. diff --git a/crates/icp/src/context/tests.rs b/crates/icp/src/context/tests.rs index f4e77d55..4e00cfa6 100644 --- a/crates/icp/src/context/tests.rs +++ b/crates/icp/src/context/tests.rs @@ -604,6 +604,8 @@ async fn test_get_agent_defaults_inside_project_with_default_local() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, @@ -669,6 +671,8 @@ async fn test_get_agent_defaults_with_overridden_local_network() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, @@ -736,6 +740,8 @@ async fn test_get_agent_defaults_with_overridden_local_environment() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, @@ -754,6 +760,8 @@ async fn test_get_agent_defaults_with_overridden_local_environment() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, diff --git a/crates/icp/src/lib.rs b/crates/icp/src/lib.rs index 2b69d97c..820d45ba 100644 --- a/crates/icp/src/lib.rs +++ b/crates/icp/src/lib.rs @@ -366,6 +366,8 @@ impl MockProjectLoader { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, @@ -384,6 +386,8 @@ impl MockProjectLoader { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, diff --git a/crates/icp/src/manifest/network.rs b/crates/icp/src/manifest/network.rs index ee35217f..02aa8ceb 100644 --- a/crates/icp/src/manifest/network.rs +++ b/crates/icp/src/manifest/network.rs @@ -67,6 +67,10 @@ pub enum ManagedMode { nns: Option, /// Configure the list of subnets (one application subnet by default) subnets: Option>, + /// Bitcoin P2P node addresses to connect to (e.g. "127.0.0.1:18444") + bitcoind_addr: Option>, + /// Dogecoin P2P node addresses to connect to + dogecoind_addr: Option>, }, } @@ -78,6 +82,8 @@ impl Default for ManagedMode { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, } } } @@ -231,7 +237,9 @@ mod tests { artificial_delay_ms: None, ii: None, nns: None, - subnets: None + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, }) }) }, @@ -259,6 +267,8 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, }) }) }, @@ -287,6 +297,60 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + }) + }) + }, + ); + } + + #[test] + fn managed_network_with_dogecoind_addr() { + assert_eq!( + validate_network_yaml(indoc! {r#" + name: my-network + mode: managed + dogecoind-addr: + - "127.0.0.1:22556" + "#}), + NetworkManifest { + name: "my-network".to_string(), + configuration: Mode::Managed(Managed { + mode: Box::new(ManagedMode::Launcher { + gateway: None, + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: Some(vec!["127.0.0.1:22556".to_string()]), + }) + }) + }, + ); + } + + #[test] + fn managed_network_with_bitcoind_addr() { + assert_eq!( + validate_network_yaml(indoc! {r#" + name: my-network + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" + "#}), + NetworkManifest { + name: "my-network".to_string(), + configuration: Mode::Managed(Managed { + mode: Box::new(ManagedMode::Launcher { + gateway: None, + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, }) }) }, diff --git a/crates/icp/src/manifest/project.rs b/crates/icp/src/manifest/project.rs index bbfae484..caa146de 100644 --- a/crates/icp/src/manifest/project.rs +++ b/crates/icp/src/manifest/project.rs @@ -289,7 +289,9 @@ mod tests { artificial_delay_ms: None, ii: None, nns: None, - subnets: None + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, }), }), })], diff --git a/crates/icp/src/network/managed/docker.rs b/crates/icp/src/network/managed/docker.rs index a73685cc..74f86872 100644 --- a/crates/icp/src/network/managed/docker.rs +++ b/crates/icp/src/network/managed/docker.rs @@ -52,6 +52,8 @@ pub struct ManagedImageOptions { pub status_dir: String, /// Parsed mounts (excluding the status directory mount, which is added at runtime). pub mounts: Vec, + /// Extra hosts entries for Docker networking (e.g. "host.docker.internal:host-gateway"). + pub extra_hosts: Vec, } impl ManagedImageOptions { @@ -160,6 +162,7 @@ impl TryFrom<&ManagedImageConfig> for ManagedImageOptions { shm_size: config.shm_size, status_dir: config.status_dir.clone(), mounts, + extra_hosts: vec![], }) } } @@ -187,6 +190,52 @@ pub enum ManagedImageConversionError { WslPathConvert { source: WslPathConversionError }, } +/// Translates a host:port address for use inside a Docker container. +/// Replaces `127.0.0.1`, `localhost`, and `::1` with `host.docker.internal` +/// so the container can reach services running on the host machine. +pub(super) fn translate_addr_for_docker(addr: &str) -> String { + if let Some((host, port)) = addr.rsplit_once(':') { + let translated = match host { + "127.0.0.1" | "localhost" | "::1" => "host.docker.internal", + _ => host, + }; + format!("{translated}:{port}") + } else { + addr.to_string() + } +} + +/// Returns extra_hosts entries needed for Docker to resolve `host.docker.internal`. +/// On Linux, Docker Engine does not provide `host.docker.internal` by default, +/// so we add `host.docker.internal:host-gateway` when any addresses reference localhost. +pub(super) fn docker_extra_hosts_for_addrs(addrs: &[String]) -> Vec { + let needs_host_gateway = addrs.iter().any(|addr| { + addr.rsplit_once(':') + .map(|(host, _)| matches!(host, "127.0.0.1" | "localhost" | "::1")) + .unwrap_or(false) + }); + if needs_host_gateway { + vec!["host.docker.internal:host-gateway".to_string()] + } else { + vec![] + } +} + +/// Translates localhost addresses in `--bitcoind-addr=` and `--dogecoind-addr=` flags +/// for use inside a Docker container. +pub(super) fn translate_launcher_args_for_docker(args: Vec) -> Vec { + args.into_iter() + .map(|arg| { + for prefix in ["--bitcoind-addr=", "--dogecoind-addr="] { + if let Some(addr) = arg.strip_prefix(prefix) { + return format!("{prefix}{}", translate_addr_for_docker(addr)); + } + } + arg + }) + .collect() +} + pub async fn spawn_docker_launcher( options: &ManagedImageOptions, ) -> Result< @@ -211,6 +260,7 @@ pub async fn spawn_docker_launcher( shm_size, status_dir, mounts, + extra_hosts, } = options; // Create status tmpdir and convert path for WSL2 if needed @@ -320,6 +370,11 @@ pub async fn spawn_docker_launcher( mounts: Some(all_mounts), binds: Some(volumes.clone()), shm_size: *shm_size, + extra_hosts: if extra_hosts.is_empty() { + None + } else { + Some(extra_hosts.clone()) + }, ..<_>::default() }), ..<_>::default() diff --git a/crates/icp/src/network/managed/launcher.rs b/crates/icp/src/network/managed/launcher.rs index f69b3d4c..3d0e65cc 100644 --- a/crates/icp/src/network/managed/launcher.rs +++ b/crates/icp/src/network/managed/launcher.rs @@ -174,6 +174,8 @@ pub fn launcher_settings_flags(config: &ManagedLauncherConfig) -> Vec { ii, nns, subnets, + bitcoind_addr, + dogecoind_addr, } = config; let mut flags = vec![]; if *ii { @@ -190,6 +192,16 @@ pub fn launcher_settings_flags(config: &ManagedLauncherConfig) -> Vec { flags.push(format!("--subnet={subnet}")); } } + if let Some(addrs) = &bitcoind_addr { + for addr in addrs { + flags.push(format!("--bitcoind-addr={addr}")); + } + } + if let Some(addrs) = &dogecoind_addr { + for addr in addrs { + flags.push(format!("--dogecoind-addr={addr}")); + } + } flags } diff --git a/crates/icp/src/network/managed/run.rs b/crates/icp/src/network/managed/run.rs index e3f78e3b..62201294 100644 --- a/crates/icp/src/network/managed/run.rs +++ b/crates/icp/src/network/managed/run.rs @@ -288,11 +288,23 @@ fn transform_native_launcher_to_container(config: &ManagedLauncherConfig) -> Man use bollard::secret::PortBinding; use std::collections::HashMap; + use super::docker::{docker_extra_hosts_for_addrs, translate_launcher_args_for_docker}; + let port = match config.gateway.port { Port::Fixed(port) => port, Port::Random => 0, }; let args = launcher_settings_flags(config); + let args = translate_launcher_args_for_docker(args); + + let all_addrs: Vec = config + .bitcoind_addr + .iter() + .chain(config.dogecoind_addr.iter()) + .flatten() + .cloned() + .collect(); + let extra_hosts = docker_extra_hosts_for_addrs(&all_addrs); let platform = if cfg!(target_arch = "aarch64") { "linux/arm64".to_string() @@ -322,6 +334,7 @@ fn transform_native_launcher_to_container(config: &ManagedLauncherConfig) -> Man shm_size: None, status_dir: "/app/status".to_string(), mounts: vec![], + extra_hosts, } } @@ -871,3 +884,131 @@ async fn install_proxy( Ok(canister_id) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::network::{Gateway, ManagedLauncherConfig, Port}; + + #[test] + fn transform_native_launcher_default_config() { + let config = ManagedLauncherConfig { + gateway: Gateway { + host: "localhost".to_string(), + port: Port::Fixed(8000), + }, + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert_eq!( + opts.image, + "ghcr.io/dfinity/icp-cli-network-launcher:latest" + ); + assert!(opts.args.is_empty()); + assert!(opts.extra_hosts.is_empty()); + assert!(opts.rm_on_exit); + assert_eq!(opts.status_dir, "/app/status"); + let binding = opts + .port_bindings + .get("4943/tcp") + .unwrap() + .as_ref() + .unwrap(); + assert_eq!(binding[0].host_port.as_deref(), Some("8000")); + } + + #[test] + fn transform_native_launcher_random_port() { + let config = ManagedLauncherConfig { + gateway: Gateway { + host: "localhost".to_string(), + port: Port::Random, + }, + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + }; + let opts = transform_native_launcher_to_container(&config); + let binding = opts + .port_bindings + .get("4943/tcp") + .unwrap() + .as_ref() + .unwrap(); + assert_eq!(binding[0].host_port.as_deref(), Some("0")); + } + + #[test] + fn transform_native_launcher_with_bitcoind_addr() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: None, + ii: true, + nns: false, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!(opts.args.contains(&"--ii".to_string())); + assert!( + opts.args + .contains(&"--bitcoind-addr=host.docker.internal:18444".to_string()) + ); + assert_eq!( + opts.extra_hosts, + vec!["host.docker.internal:host-gateway".to_string()] + ); + } + + #[test] + fn transform_native_launcher_with_dogecoind_addr() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: Some(50), + ii: false, + nns: true, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: Some(vec!["localhost:22556".to_string()]), + }; + let opts = transform_native_launcher_to_container(&config); + assert!(opts.args.contains(&"--nns".to_string())); + assert!(opts.args.contains(&"--artificial-delay-ms=50".to_string())); + assert!( + opts.args + .contains(&"--dogecoind-addr=host.docker.internal:22556".to_string()) + ); + assert_eq!( + opts.extra_hosts, + vec!["host.docker.internal:host-gateway".to_string()] + ); + } + + #[test] + fn transform_native_launcher_external_addr_no_extra_hosts() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: Some(vec!["192.168.1.5:18444".to_string()]), + dogecoind_addr: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!( + opts.args + .contains(&"--bitcoind-addr=192.168.1.5:18444".to_string()) + ); + assert!(opts.extra_hosts.is_empty()); + } +} diff --git a/crates/icp/src/network/mod.rs b/crates/icp/src/network/mod.rs index d01d24cb..afcecafa 100644 --- a/crates/icp/src/network/mod.rs +++ b/crates/icp/src/network/mod.rs @@ -90,6 +90,8 @@ pub struct ManagedLauncherConfig { pub ii: bool, pub nns: bool, pub subnets: Option>, + pub bitcoind_addr: Option>, + pub dogecoind_addr: Option>, } #[derive( @@ -128,6 +130,8 @@ impl ManagedMode { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })) } } @@ -215,6 +219,8 @@ impl From for Configuration { ii, nns, subnets, + bitcoind_addr, + dogecoind_addr, } => { let gateway: Gateway = match gateway { Some(g) => g.into(), @@ -228,6 +234,8 @@ impl From for Configuration { ii: ii.unwrap_or(false), nns: nns.unwrap_or(false), subnets, + bitcoind_addr, + dogecoind_addr, })), }, } @@ -377,3 +385,49 @@ impl Access for MockNetworkAccessor { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::manifest::network::{ + Gateway as ManifestGateway, Managed as ManifestManaged, ManagedMode as ManifestManagedMode, + Mode, + }; + + #[test] + fn from_mode_launcher_with_bitcoind_addr() { + let mode = Mode::Managed(ManifestManaged { + mode: Box::new(ManifestManagedMode::Launcher { + gateway: Some(ManifestGateway { + host: None, + port: Some(8000), + }), + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, + }), + }); + + let config: Configuration = mode.into(); + match config { + Configuration::Managed { + managed: + Managed { + mode: ManagedMode::Launcher(launcher_config), + }, + } => { + assert_eq!( + launcher_config.bitcoind_addr, + Some(vec!["127.0.0.1:18444".to_string()]) + ); + assert_eq!(launcher_config.dogecoind_addr, None); + assert!(!launcher_config.ii); + assert!(!launcher_config.nns); + } + _ => panic!("expected ManagedMode::Launcher"), + } + } +} diff --git a/crates/icp/src/project.rs b/crates/icp/src/project.rs index a10352bd..fa55f41a 100644 --- a/crates/icp/src/project.rs +++ b/crates/icp/src/project.rs @@ -318,6 +318,8 @@ pub async fn consolidate_manifest( ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, })), }, }, diff --git a/docs/guides/containerized-networks.md b/docs/guides/containerized-networks.md index 39bc4452..2157781c 100644 --- a/docs/guides/containerized-networks.md +++ b/docs/guides/containerized-networks.md @@ -158,6 +158,27 @@ networks: - POCKET_IC_MUTE_SERVER=false ``` +### Passing Arguments to the Container + +Use the `args` field to pass command-line arguments to the container's entrypoint. This is how you configure image-specific behavior such as enabling Internet Identity, NNS, Bitcoin integration, or other flags supported by the image: + +```yaml +networks: + - name: docker-local + mode: managed + image: ghcr.io/dfinity/icp-cli-network-launcher + port-mapping: + - "8000:4943" + args: + - "--ii" +``` + +The `args` field passes values directly to the container entrypoint with no processing. The Docker image determines what arguments it accepts — see the image's documentation for available options. + +**Comparison with native launcher mode:** When using native managed networks (without `image`), settings like `bitcoind-addr`, `ii`, `nns`, and `subnets` are configured as top-level YAML fields. In Docker image mode, these are passed via `args` instead, since the image could be any Docker image — not necessarily the official network launcher. + +> **Docker networking note:** When referencing services running on the host machine from inside a container (e.g., a local Bitcoin node), use `host.docker.internal` instead of `127.0.0.1` or `localhost`. Inside a container, `127.0.0.1` refers to the container's own loopback, not the host. For example: `--bitcoind-addr=host.docker.internal:18444`. Docker Desktop (macOS/Windows) resolves `host.docker.internal` automatically. On Linux Docker Engine, you may need to pass `--add-host=host.docker.internal:host-gateway` or equivalent to ensure it resolves. + ### Remove Container on Exit Automatically delete the container when stopped: diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index d9da1ce5..9d317108 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -186,8 +186,15 @@ type: https://example.com/recipe.hb.yaml ## Networks +Networks define where canisters are deployed. There are two modes: + +- **Managed** (`mode: managed`): A local test network launched and controlled by icp-cli. Can run [natively](#managed-network) or in a [Docker container](#docker-network). +- **Connected** (`mode: connected`): A remote network accessed by URL. + ### Managed Network +A managed network runs the [network launcher](https://github.com/dfinity/icp-cli-network-launcher) natively on your machine. To run it in a Docker container instead, see [Docker Network](#docker-network). + ```yaml networks: - name: local-dev @@ -203,14 +210,24 @@ networks: | `mode` | string | Yes | `managed` | | `gateway.host` | string | No | Host address (default: localhost) | | `gateway.port` | integer | No | Port number (default: 8000, use 0 for random) | -| `artificial_delay_ms` | integer | No | Artificial delay to add to every update call (ms) | -| `ii` | boolean | No | Set up Internet Identity canister (default: false) | -| `nns` | boolean | No | Set up NNS canisters (default: false) | -| `subnets` | array | No | Configure subnet types (default: one application subnet) | +| `artificial-delay-ms` | integer | No | Artificial delay for update calls (ms) | +| `ii` | boolean | No | Install Internet Identity canister (default: false). Also implicitly enabled by `nns`, `bitcoind-addr`, and `dogecoind-addr`. | +| `nns` | boolean | No | Install NNS and SNS canisters (default: false). Implies `ii` and adds an SNS subnet. | +| `subnets` | array | No | Configure subnet types. See [Subnet Configuration](#subnet-configuration). | +| `bitcoind-addr` | array | No | Bitcoin P2P node addresses (e.g. `127.0.0.1:18444`). Adds a bitcoin and II subnet. | +| `dogecoind-addr` | array | No | Dogecoin P2P node addresses. Adds a bitcoin and II subnet. | + +For full details on how these settings interact, see the [network launcher CLI reference](https://github.com/dfinity/icp-cli-network-launcher#cli-reference). + +> **Note:** These settings apply to native managed networks only. For [Docker image mode](#docker-network), pass equivalent flags via the `args` field instead. #### Subnet Configuration -Configure the local network's subnet layout. By default, a single application subnet is created. Use multiple subnets to test cross-subnet (Xnet) calls: +Configure the local network's subnet layout: + +- **Default** (no `subnets` field): one application subnet is created. +- **With `subnets`**: only the listed subnets are created — the default application subnet is **replaced**, not extended. Add `application` explicitly if you still need it. +- An **NNS subnet** is always created regardless of configuration (required for system operations). ```yaml networks: @@ -224,7 +241,32 @@ networks: Available subnet types: `application`, `system`, `verified-application`, `bitcoin`, `fiduciary`, `nns`, `sns` -**Note:** Subnet type support depends on the network launcher version. The `application` type is commonly used for testing. +#### Bitcoin and Dogecoin Integration + +Connect the local network to a Bitcoin or Dogecoin node for testing chain integration: + +```yaml +networks: + - name: local + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" +``` + +The `bitcoind-addr` field specifies the P2P address (not RPC) of the Bitcoin node. Multiple addresses can be specified. Dogecoin integration works the same way via `dogecoind-addr`. Both can be configured simultaneously. + +**Implicit effects:** When `bitcoind-addr` or `dogecoind-addr` is configured, the network launcher automatically adds a **bitcoin** subnet and an **II** subnet (provides threshold signing keys required for chain operations). If you also explicitly specify `subnets`, you must include `application` to keep the default application subnet: + +```yaml +networks: + - name: local + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" + subnets: + - application + - system +``` ### Connected Network @@ -245,6 +287,8 @@ networks: ### Docker Network +A managed network can also run inside a Docker container. Adding the `image` field switches from native to Docker mode: + ```yaml networks: - name: docker-local @@ -254,7 +298,24 @@ networks: - "0:4943" ``` -See [Containerized Networks](../guides/containerized-networks.md) for full options. +To configure image-specific behavior (e.g., enabling Internet Identity, NNS, or Bitcoin integration), use the `args` field to pass command-line arguments to the container entrypoint: + +```yaml +networks: + - name: docker-local + mode: managed + image: ghcr.io/dfinity/icp-cli-network-launcher + port-mapping: + - "8000:4943" + args: + - "--ii" +``` + +The available arguments depend on the Docker image — see the image's documentation for details. + +> **Docker networking note:** When referencing services running on the host machine from inside a container (e.g., a local Bitcoin node), use `host.docker.internal` instead of `127.0.0.1` or `localhost`. Inside a container, `127.0.0.1` refers to the container's own loopback, not the host. For example: `--bitcoind-addr=host.docker.internal:18444`. Docker Desktop (macOS/Windows) resolves `host.docker.internal` automatically. On Linux Docker Engine, you may need to pass `--add-host=host.docker.internal:host-gateway` or equivalent to ensure it resolves. + +See [Containerized Networks](../guides/containerized-networks.md) for full configuration options. ## Environments diff --git a/docs/schemas/icp-yaml-schema.json b/docs/schemas/icp-yaml-schema.json index 08ba9d5b..94f21985 100644 --- a/docs/schemas/icp-yaml-schema.json +++ b/docs/schemas/icp-yaml-schema.json @@ -500,6 +500,26 @@ "null" ] }, + "bitcoind-addr": { + "description": "Bitcoin P2P node addresses to connect to (e.g. \"127.0.0.1:18444\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "dogecoind-addr": { + "description": "Dogecoin P2P node addresses to connect to", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "gateway": { "anyOf": [ { diff --git a/docs/schemas/network-yaml-schema.json b/docs/schemas/network-yaml-schema.json index 9b80d66b..6f4655b5 100644 --- a/docs/schemas/network-yaml-schema.json +++ b/docs/schemas/network-yaml-schema.json @@ -159,6 +159,26 @@ "null" ] }, + "bitcoind-addr": { + "description": "Bitcoin P2P node addresses to connect to (e.g. \"127.0.0.1:18444\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "dogecoind-addr": { + "description": "Dogecoin P2P node addresses to connect to", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "gateway": { "anyOf": [ {