Run a Shade Tree node.

One container, one record URL. The node opens the HTTPS tunnels agents ask for and never learns who asked.

What a node does

A Shade Tree node is an onion service. An agent's proxy reaches it over Tor, shows a zero-knowledge proof that its member has staked, and asks for one HTTPS tunnel to port 443. The node checks the proof against the current member set on Sepolia, opens the connection, and relays bytes. The destination sees the node's IP. The node sees a proof and a destination, never the member. Every node that joins makes the canopy's anonymity set larger and the service harder to take down.

What it needs

A Linux box with Docker, 1 vCPU, 2 GB of memory, 10 GB of disk and outbound internet. No inbound ports: the node is reached as an onion service, so a firewall that allows only SSH in is correct. The Sepolia canopy lists only nodes whose operator holds a bond in the gateway registry, so you also need a Sepolia address with a little testnet ETH: the bond is 0.001 ETH plus gas, it is yours, and it is the only step that costs anything.

1. Check the box

docker run --rm -e SHADENET_RECORD=https://raw.githubusercontent.com/dmarzzz/shade-tree-node/main/network/sepolia/deployment.json ghcr.io/dmarzzz/shadenet-node:0.7.1 check --probe

This starts nothing. It reads the record, answers each RPC in it, verifies the Tor configuration, measures free disk, tries the two loopback ports, reaches every Elder Tree over a temporary Tor, and prints the exact environment the node would run with. Fix any FAIL line, then continue.

2. Stake the operator and sign the node's name

docker run --rm -v shadenet-node:/state -e SHADENET_RECORD=https://raw.githubusercontent.com/dmarzzz/shade-tree-node/main/network/sepolia/deployment.json ghcr.io/dmarzzz/shadenet-node:0.7.1 identity

This mints the node's onion address and keeps it in the volume. Then, on the machine that holds your operator key, stake the bond once and sign the onion. The key never has to reach the node's box.

read -s KEY && SHADE_TREE_REGISTER_KEY="$KEY" docker run --rm -e SHADE_TREE_REGISTER_KEY --entrypoint node ghcr.io/dmarzzz/shadenet-node:0.7.1 packages/node/bin/shade-tree.mjs register-gateway --gateway-registry 0x94ECeD0C1c7a8793a5c901c8C1995C8E7039A868 --rpc-url https://rpc.sepolia.ethpandaops.io; unset KEY
docker run --rm -v "$PWD":/k:ro ghcr.io/dmarzzz/shadenet-node:0.7.1 authorize --onion <onion> --key-file /k/operator.key

The key file holds one line of 64 hex characters, readable only by you. authorize prints SHADENET_OPERATOR and SHADENET_OPERATOR_SIG; both are safe to put on the node. Without them every Elder Tree refuses the node not-staked. One bond covers every node you run.

3. Run the node

docker run -d --name shadenet-node --restart unless-stopped --security-opt no-new-privileges:true --cap-drop ALL --read-only --tmpfs /tmp --memory 1g --log-opt max-size=20m -e SHADENET_RECORD=https://raw.githubusercontent.com/dmarzzz/shade-tree-node/main/network/sepolia/deployment.json -e SHADENET_OPERATOR=0x… -e SHADENET_OPERATOR_SIG=0x… -v shadenet-node:/state ghcr.io/dmarzzz/shadenet-node:0.7.1

Fill in the two values from step 2. The extra flags drop every privilege the node does not use and cap its memory and logs; the runbook explains each one and has a compose file for people who prefer compose.

The first ten minutes

The log tells the story in order. The node mints its onion identity and keeps it in the volume. Tor bootstraps, usually within two minutes, and the onion descriptor is published about thirty seconds later. The heartbeat announces to every Elder Tree in the record and logs heartbeat accepted once per Elder; a heartbeat rejected line names the Elder's reason and the fix. docker exec shadenet-node node packages/node/bin/shadenet-node.mjs status shows how many Elder Trees list the node. Within fifteen minutes it is counted on the Network page. The first tunnel is served after the node's next member-set refresh, about a minute after that.

The knobs

Everything the node needs is in the record. The rest is ten optional settings, as SHADENET_* variables or the same keys in /state/node.toml: who to admit, an egress allow and deny list, the selection weight that bounds how much traffic lands here, a region, the metrics port, the log format, the operator key or signature, and onion proof-of-work. shadenet-node help lists them with defaults; the configuration reference maps each to the advanced layer underneath.

What you admit

A node admits the staked set named in its record. It can admit more than one: SHADENET_SETS=0x…@deployBlock adds another canopy's set, each scanned from its own deploy block, so one node can serve a staging set and the production set at once. The invited path (your own members file) and the paid path from ADR 0008 switch on with SHADENET_ADMIT. The directory advertises what each node admits, so an agent is only ever routed to a node that will take its proof.

What it costs

The canopy's own nodes are $12 a month droplets with 2000 GiB of outbound transfer included. A member who saturates every slot relays about 40 MiB a minute, so three continuously saturated tier-1 members fill that allowance before overage starts at a cent per GiB. Most members use a small fraction of a slot. The weight knob is the lever: a lower weight sends fewer members this way. Bonds are refundable collateral on a testnet, not revenue; the economics note has every number with its source.

What lands on your address

Destinations see the node's IP, so abuse complaints come to the operator. The node rejects private and non-public destination addresses after DNS resolution, allows only port 443 by default, and takes an allow and deny list for anything narrower. A member who spends more than their budget is slashed on chain and burned from the set. Retiring is docker stop: the node leaves every Elder Tree's directory within fifteen minutes, open tunnels finish, and the identity stays in the volume until you delete it.

Day two