Skip to main content

Running the node

There are two supported ways to run a Demos node:
  • Track 1 — Docker Compose (recommended). Single-command bring-up of the full stack: node + Postgres + TLSNotary + monitoring. Pick this unless you have a specific reason not to.
  • Track 2 — bare metal ./run (advanced). Node binary runs natively on the host via Bun; only Postgres lives in a sidecar container managed by ./run. Pick this for core development, kernel-level debugging, or TUI-driven operation.
Both tracks start from the same repo (git clone https://github.com/kynesyslabs/node.git) and the same .env file (cp .env.example .env). They differ only in where the node binary and its dependencies live. For the full install walkthrough see INSTALL.md; for variable-by-variable settings see Node configuration.

Prerequisites

  • Docker 20.10+
  • The docker compose v2 plugin (with a space — the legacy hyphenated docker-compose Python script is not supported)
  • git
docker --version
docker compose version

Start the node

From the repo root:
cp .env.example .env       # defaults are fine for local dev
docker compose up
The first run pulls images and builds the node container, which can take a few minutes. Subsequent starts are near-instant. When the stack is healthy:
  • Node RPC: http://localhost:53550 (try curl http://localhost:53550/info)
  • Grafana: http://localhost:3000 (default admin / demos)
  • Prometheus: http://localhost:9091

First boot: identity generation

On the very first start the node entrypoint:
  1. Generates a fresh ed25519 keypair if .demos_identity does not exist in the demos_node_state volume.
  2. Writes the private key into the volume at .demos_identity.
  3. Writes the public key to a publickey_<timestamp> file alongside it and prints it in the container logs.
Tail the logs to grab your public key:
docker compose logs -f node
Your identity now persists across docker compose down / up cycles. It is destroyed by docker compose down -v — back it up first (see Backing up and restoring a node).

Common operations

# Follow node logs
docker compose logs -f node

# Follow everything
docker compose logs -f

# Update to the latest source
git pull
docker compose up -d --build

# Stop containers, KEEP volumes (identity, chain data, dashboards)
docker compose down

# Stop AND DELETE all volumes — nuclear, destroys identity + state
docker compose down -v
State persists in named Docker volumes prefixed demos_:
VolumeHolds
demos_node_state.demos_identity, demos_peerlist.json, .tlsnotary-key, output/
demos_pgdataPostgreSQL data directory (chain state, indexes)
demos_node_dataBundled bootstrap data (genesis.json, evmChains, l2ps) plus runtime artefacts
demos_node_logsNode logs
demos_grafana_dataGrafana dashboards, users, settings
demos_prometheus_dataPrometheus TSDB
demos_neo4j_data / demos_neo4j_logsNeo4j (only when the neo4j profile is on)
The demos_ prefix is set explicitly in docker-compose.yml, so volume names are stable regardless of the directory you ran compose from.

Compose profiles

COMPOSE_PROFILES in .env controls the optional services. The defaults are tuned for local development.
ProfileAdds
(none)postgres + node — bare minimum
tlsnotaryTLSNotary sidecar (HTTPS attestation)
monitoringPrometheus + Grafana
fullnode-exporter (host CPU/RAM/disk metrics) — pair with monitoring
neo4jNeo4j (only for CGC/KYC features)
# Default: postgres + node + tlsnotary + monitoring (uses .env)
docker compose up

# Minimal: only postgres + node (also set TLSNOTARY_ENABLED=false in .env)
COMPOSE_PROFILES= docker compose up

# Add host-level metrics
COMPOSE_PROFILES=monitoring,tlsnotary,full docker compose up

# Add Neo4j (only if you actually need CGC/KYC)
COMPOSE_PROFILES=monitoring,tlsnotary,neo4j docker compose up

Going public

Before joining a real Demos network from a machine that other peers should reach:
  1. Set EXPOSED_URL in .env to your reachable address (public IP or DNS), not localhost.
  2. Open inbound TCP for RPC_PORT (53550), OMNI_PORT (53551), and 7047 (TLSNotary, if enabled) on your firewall before advertising the public URL.
  3. Seed demos_peerlist.json with bootstrap peers from the team — see Joining the testnet using a custom genesis.
Do not expose 5432 (Postgres), 9090 (node metrics), 9091 (Prometheus), or 3000 (Grafana) to the public internet. Keep them behind your firewall or VPN.

Track 2: Bare metal ./run (advanced)

In Track 2 the node binary runs natively under Bun, and only Postgres lives in Docker (as a sidecar managed by ./run).

Prerequisites

  • Docker (still needed for the Postgres sidecar)
  • Bun (via Mise or the direct installer)
  • Rust (required by ./scripts/install-deps.sh to build wstcp)
  • A .env with the bare-metal overrides:
    PG_HOST=localhost
    PG_PORT=5332
    TLSNOTARY_HOST=localhost
    
The full prerequisite walkthrough lives in INSTALL.md — Track 2.

Start the node

./run
That spins up the Postgres sidecar (host-mapped on 5332), runs the node natively, and opens the TUI. Stopping the node with Ctrl+C (or Q in the TUI) shuts the sidecar down too.
./run at the repo root is a thin wrapper that forwards to scripts/run (the actual implementation). Always invoke ./run from the repo root — never call scripts/run directly. The wrapper exists so the legacy command path keeps working.

First boot: identity generation

The very first ./run:
  1. Generates a fresh ed25519 keypair if .demos_identity does not exist.
  2. Writes the private key to .demos_identity in the repo root.
  3. Writes the public key to publickey_<timestamp> and prints it on the console.
Press Ctrl+C (or Q in the TUI) to stop, then edit .env / demos_peerlist.json before restarting. Set tight permissions on the identity file:
chmod 600 .demos_identity

./run flags

FlagArgumentDescription
-p<port>Node RPC port (default 53550).
-d<port>Postgres host-mapped port (default 5332).
-i<path>Identity file path (default .demos_identity).
-ctrue/falseClean the Postgres database on startup (default false).
-ntrue/falseSkip git pull (useful for custom branches).
-u<url>Override EXPOSED_URL.
-l<path>Peer list file (default demos_peerlist.json).
-r<runtime>Force runtime — bun only (node runtime is deprecated).
-btrue/falseRestore from the most recent backup in output/.
-v(no arg)Verbose logging.
-h(no arg)Show the help message.
-t / --no-tui(no arg)Disable the TUI.
Examples:
./run                          # Start with default settings
./run -p 53551 -d 5333         # Run on custom ports
./run -c true                  # Clean start (fresh database)
./run -v                       # Verbose output for troubleshooting
./run -n true                  # Skip git update
./run -t                       # Disable the TUI

TUI controls

By default ./run launches a terminal UI with tabs for Core, Network, Chain, Consensus, and others.
KeyAction
Number keys (1, 2, 3, …)Switch to the corresponding tab
Arrow keysScroll within the current tab
HHelp
QQuit (graceful shutdown, also stops the Postgres sidecar)
Disable the TUI with ./run -t or ./run --no-tui if you prefer plain log output (useful in tmux, CI, or when piping to a file).

Verify the node is running

In a separate terminal:
# Liveness
curl http://localhost:53550
curl http://localhost:53550/info

# Confirm ports are bound
sudo lsof -i :53550   # node RPC
sudo lsof -i :5332    # Postgres sidecar

# Confirm the Postgres container is up
docker ps

Stop the node

Press Ctrl+C (or Q in the TUI) in the ./run terminal. To stop the Postgres sidecar manually:
cd postgres_5332
./stop.sh

Reset chain state without losing identity

./run -c true       # wipe Postgres on startup; identity in .demos_identity is kept
If you are connected to peers, the node will resync from them. If you are running solo, the chain restarts from the genesis block.

Bare-metal troubleshooting

SymptomFix
Port already in usesudo lsof -i :5332 to identify, then ./run -d 5333 (or pick another port).
Docker permission deniedsudo usermod -aG docker $USER && newgrp docker.
Database connection timeoutsudo systemctl restart docker, then cd postgres_5332 && ./clean.sh && ./start.sh.
Missing dependenciesrm -rf node_modules bun.lockb && bun install.