Run a Validator Node
Everything on this page happens before the Orbinum team hears from you. By
the end your node is synced and your session keys are registered on-chain — which
is exactly the state addValidator requires.
A server meeting the validator requirements, Docker installed, and a GHCR token — see Installation.
1. Authenticate with GHCR
The node image is private. Log in once per host with a personal access token
scoped to read:packages:
echo <GITHUB_TOKEN> | docker login ghcr.io -u <github_user> --password-stdin
Piping the token keeps it out of your shell history. Docker stores the credential
in ~/.docker/config.json, so this survives reboots and automatic image updates.
2. Clone node-deploy and configure
The Compose file and the chain spec ship together:
git clone https://github.com/orbinum/node-deploy.git
cd node-deploy/testnet/validator
cp .env.example .env
Every variable is documented in the file. These are the ones that matter:
| Variable | Value | Why |
|---|---|---|
VALIDATOR_NAME | anything identifiable | Shown on telemetry |
VALIDATOR_NODE_KEY | openssl rand -hex 32 | Your stable libp2p identity |
TELEMETRY_URL | leave the default | Level 1 publishes your validator address to the dashboard |
METRICS_BIND | your private IP, or leave 127.0.0.1 | The default fails closed and exposes nothing |
PUBLIC_ADDR | leave empty | The node auto-detects its public address |
RESERVED_NODES | leave empty | See the warning below |
RESERVED_NODES emptyRESERVED_NODES exists for Orbinum's own validators, which sit on a private
subnet and pin each other with --reserved-only. That flag restricts the node to
peers on its reserved list — including refusing the bootnodes.
Setting it on a public validator with an incomplete list isolates the node: zero
peers, stuck at block #0, no error in the logs. An independent operator leaves
it empty and discovers the network normally.
testnet-spec.json already carries them, and the Compose file already mounts it.
There is nothing to fetch, paste, or add yourself to.
3. Open the firewall
sudo ufw allow 30333/tcp # P2P — must be reachable from the internet
sudo ufw allow 22/tcp # SSH
sudo ufw enable
No rule for 9944, and none for 9615 unless your monitoring host is on a
private network that needs it.
4. Start the node
docker compose pull
docker compose up -d
docker compose logs -f orbinum-validator
The Compose file already passes --validator, --no-mdns, the chain spec and the
telemetry flags — there is nothing to add on the command line.
Watch the logs for the node's own peer identity at startup, then a stream of import messages while it catches up. Wait until it reports idling at the chain head before continuing; inserting keys into a syncing node works, but you cannot tell a healthy node from a stalled one until it has caught up.
.env needs a recreate, not a restartA container's command line is fixed when it is created, so docker compose restart brings the node back with its old arguments and an edited .env appears
to do nothing:
docker compose up -d --force-recreate orbinum-validator
To see what a running node actually received:
docker inspect orbinum-validator --format '{{join .Config.Cmd " "}}'
5. Confirm it is synced
docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"system_health"}' \
http://localhost:9944
{ "jsonrpc": "2.0", "result": { "peers": 4, "isSyncing": false, "shouldHavePeers": true }, "id": 1 }
You want isSyncing: false and peers of at least 2. peers: 0 means the node
cannot reach the bootnodes — check that port 30333 is genuinely reachable from
outside, and that RESERVED_NODES is empty.
Note the docker exec: port 9944 is not published to the host, so this command
runs inside the container. That is deliberate — see
requirements.
6. Generate session keys
docker exec orbinum-validator curl -s -H 'Content-Type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"author_rotateKeys"}' \
http://localhost:9944
The result is a 0x string of 128 hex characters — 64 bytes, being your Aura
sr25519 public key followed by your GRANDPA ed25519 public key, in the order
the runtime declares them.
The private halves were written into the node's keystore at
/data/chains/orbinum_testnet/keystore and never left the server. Only the public
blob above goes on-chain.
Calling author_rotateKeys again generates a new pair and the blob you saved
becomes stale. If you rotate after registering, you must submit session.setKeys
again with the new value or your node stops authoring at the next session.
Copy the blob. You need it in the next step.
7. Submit session.setKeys
This is an on-chain extrinsic signed by your validator account — the account you will send to the Orbinum team.
- Open Polkadot.js Apps and connect it to
wss://rpc-1.testnet.orbinum.io. - Go to Developer → Extrinsics.
- Select your validator account, then
session→setKeys(keys, proof). - Paste the blob from step 6 into
keys. - Put
0xintoproof. - Submit and sign.
proof is unused by this runtime, so the empty value is correct — it exists for
chains that require a possession proof over the session keys.
Verify it landed. In Developer → Chain state, query
session.nextKeys(yourAccount). It must return the same blob you submitted. If it
returns nothing, the extrinsic did not finalize and the next step will fail.
addValidator is rejected with NoSessionKeys unless this extrinsic has already
finalized. Do not email the team before session.nextKeys returns your blob.
Next Steps
- Apply to Join the Set — send your account to the Orbinum team
- Validator Operations — monitoring, backups and troubleshooting