Docker-based setup for running a canopy validator (or full node).
- A server with
dockerinstalled - Validators only: a domain name with a DNS
*.<DOMAIN>wildcard A record pointing at the server's IP.
Copy docker-compose.yml and the config/ directory to the server on the same base directory,
then:
Note
You may also clone the whole repository with git clone to skip copying each file individually.
# generate the validator key by writing config/keystore.json + config/validator_key.json
docker compose run --rm --no-deps -it keygen
# validators only: modify the "externalAddress" field in config/config.json to point to your domain
# config/config.json → "externalAddress": "tcp://<your-domain>"
# start the node
docker compose up -dIf you also copy the Makefile, the equivalent shortcuts are make init-keys and make up (see
Operations).
Skip keygen. Instead, copy your existing config.json, keystore.json, and validator_key.json
into config/ before running docker compose up -d.
You may also need to update config.json so rpcURL is "/rpc" and adminRPCUrl is
"/adminrpc".
docker-compose.monitoring.yml runs Caddy (reverse proxy + auto-HTTPS) and the
prometheus/grafana/loki/alloy stack. Caddy is also what exposes the wallet, explorer, and RPC
endpoints for public access. The node container only publishes its P2P port (9001) on its own, so
skip this step only if you don't need those endpoints reachable from outside the box.
Copy docker-compose.monitoring.yml to the same directory as the previous compose file, along with the monitoring folder, then:
cp .env.example .env
# edit .env: set DOMAIN=<your-domain> (defaults to localhost),
# change AUTH_PASSWORDHASH for production (docker run --rm -it caddy:2.11-alpine caddy hash-password
# or make hash-password), if the docker command is used, the $ must be escaped as $$ and set GF_ADMIN_PASSWORD
docker compose -f docker-compose.monitoring.yml up -dLocal development: leave DOMAIN=localhost, you can still access through <SERVICE>.localhost.
Production: point the *.<DOMAIN> wildcard at the box and certificates are issued automatically via
Let's Encrypt.
| Service | URL | Auth |
|---|---|---|
| Wallet | https://wallet.<DOMAIN>/ |
basic-auth |
| Explorer | https://explorer.<DOMAIN>/ |
open |
| RPC | https://rpc.<DOMAIN>/ |
open |
| Admin RPC | https://adminrpc.<DOMAIN>/ |
basic-auth |
| Grafana | https://monitoring.<DOMAIN>/ |
grafana login |
| Loki | https://loki.<DOMAIN>/ |
basic-auth |
Sync from a mainnet snapshot instead of genesis. Copy scripts/snapshot.sh to the server, then:
SNAPSHOT_URL=<url> ./scripts/snapshot.shRun plainly (make snapshot), it restores the root chain (SNAPSHOT_URL -> data/canopy)
and every added nested chain (docker-compose.chain-*.yml) from their snapshots: it stops the
services, downloads all snapshots concurrently, wipes & extracts each chain's db dir, then
starts them all together. SNAPSHOT_URL (root chain) can come from .env; nested-chain snapshots
come from https://public-snapshots.app.canopynetwork.org/chain/<id>. Only each chain's db dir is
wiped, so the shared keystore.json/validator_key.json are preserved.
You can also run make snapshot-up to additionally bring up monitoring after the snapshots are
applied (see Operations).
The Makefile wraps both compose files together (docker compose -f docker-compose.yml -f docker-compose.monitoring.yml), so its targets act on node + monitoring as one stack:
make help # all targets
make pull # pull the latest canopynetwork/canopy image
make gen-key # first boot only: generate the validator key
make up # start everything
make logs # follow node logs
make status # show container status
make restart # apply config/config.json edits
make snapshot # restore root chain + all added nested chains from snapshots (concurrent) and start them
make snapshot-up # same as snapshot, then also bring up monitoring
make add-nested-chain CHAIN_ID=29 # assemble a nested chain's files (see below)
make add-nested-chain-snapshot CHAIN_ID=29 # assemble + restore its snapshot (start with 'make up')
make down # stop everythingmake up, make node-up, make down, and make status automatically include
every docker-compose.chain-*.yml that make add-nested-chain has generated, so
the root chain and all added nested chains start/stop/report together.
A node can run extra ("nested") chains alongside the root chain. Each nested
chain runs as its own container that connects to the root chain already running
in this stack. Two targets, both backed by scripts/add-nested-chain.sh:
make add-nested-chain CHAIN_ID=29 # assemble files only (does NOT start)
make add-nested-chain-snapshot CHAIN_ID=29 # assemble (if needed) + restore snapshot (does NOT start)Assembling the files (add-nested-chain) only prepares the chain; it never
starts a container:
- Fetches the chain's
genesis.jsonandconfig.jsonfrom the Canopy chain-data API (https://api.app.canopynetwork.org/api/v1). - Fetches the chain's plugin files if it has any (a
404means it has none and is skipped silently) and writes them where the chain expects them. If the plugin needs env vars, they are documented in.env.exampleand you are prompted to add them to your.env. - Writes the fetched
config.jsonverbatim, including itsrootChainentry as served by the launchpad (it is not rewritten to point at this node). - Reuses this node's
config/keystore.jsonandconfig/validator_key.jsonverbatim (the node identity is identical across every chain). Runmake gen-keyfirst if you don't have them yet. - Writes everything under
data/chain-<id>/and generatesdocker-compose.chain-<id>.yml, using the Docker image that matches the chain's plugin language (canopynetwork/canopy-plugin-<lang>:latest, defaulting togo).
Starting from genesis — once the files are assembled, start the whole stack
(root chain + every added nested chain) with make up (or make node-up for no
monitoring). These targets pick up every docker-compose.chain-*.yml
automatically.
Restoring a snapshot (add-nested-chain-snapshot) assembles the files if
needed, then reuses scripts/snapshot.sh to restore the chain's snapshot from
https://public-snapshots.app.canopynetwork.org/chain/<id> into data/chain-<id>/canopy.
It does not start the chain — like add-nested-chain, it only prepares the
data; start it (and every other added chain) afterwards with make up. The
snapshot only ever touches that chain's db dir, so the shared
keystore.json/validator_key.json are never overwritten and the root chain and
other nested chains are left untouched. (To restore and start everything at once
instead, use make snapshot.)
Both are idempotent: if data/chain-<id> already exists the files are reused, not
re-fetched. To recreate a chain from scratch, remove data/chain-<id> and
docker-compose.chain-<id>.yml first.
Upgrading canopy: make pull && make up (or docker compose pull node && docker compose up -d).
The node also self-updates in place via canopy's auto-update coordinator when autoUpdate is
enabled in config (enabled by default).
docker-compose.yml # node (+ keygen, run once via `docker compose run keygen`)
docker-compose.monitoring.yml # caddy + monitoring stack (configs inlined via `configs:`)
config/ # node config + genesis (bind-mounted into the node)
data/ # runtime state (peers, chain db), gitignored
scripts/ # snapshot.sh, add-nested-chain.sh