Skip to content

Docker Deployment

NOOSChain ships one Linux container image containing the compiled Node.js node, the noos operator CLI, SQL migrations, and the native HashiCorp Raft, Nervos SMT, and Wasmtime binaries. PostgreSQL remains a separate service.

The Node.js process owns the native child-process lifecycle. HashiCorp Raft is supervised as the consensus engine, Nervos SMT is supervised as the state-root engine, and Wasmtime is started locally when contract preflight or execution needs it. Nervos and Wasmtime communicate over stdin/stdout and expose no network ports.

Build The Local Image

powershell
npm run docker:build

This produces nooschain:local. The tag means the image was built locally; it is not a production release tag.

The multi-stage build includes:

  • dist/index.js and the compiled noos CLI;
  • all SQL files under dist/db/migrations;
  • /usr/local/bin/hashicorp-raft;
  • /usr/local/bin/noos-state-root-sidecar-nervos;
  • /usr/local/bin/noos-contract-executor-wasmtime;
  • production Node.js dependencies only.

The final process runs as the non-root nooschain user under tini so signals are forwarded and child processes are reaped.

Edit The Local Configuration

Compose uses the checked-in development examples by default. To keep local changes outside those examples, copy them into the ignored docker/config directory:

powershell
Copy-Item docker/env/development-validator.env.example docker/config/validator.env
Copy-Item docker/env/development-observer.env.example docker/config/observer.env
$env:NOOS_VALIDATOR_ENV_FILE = "./config/validator.env"
$env:NOOS_OBSERVER_ENV_FILE = "./config/observer.env"

The paths are resolved relative to docker/compose.local.yaml. Edit the files directly, use the admin GUI configuration editor, or use the packaged helper:

powershell
docker compose -f docker/compose.local.yaml run --rm cli `
  config edit --profile development-validator --file /config/validator.env

docker compose -f docker/compose.local.yaml run --rm cli `
  config validate --profile development-validator --file /config/validator.env

Keep the two PowerShell environment variables set when running Compose. In CI or another shell, set the equivalent variables in that process environment.

Validate And Start Compose

Validate interpolation without starting services:

powershell
npm run docker:config

Start PostgreSQL, migrations, and the validator:

powershell
npm run docker:up
docker compose -f docker/compose.local.yaml ps

The startup order is PostgreSQL health, the one-shot validator migration, then the validator. Migrations are not run automatically by every node process.

Stop containers without deleting persistent data:

powershell
npm run docker:down

Deleting volumes is intentionally not part of the ordinary stop command.

Automatic Raft Block Production

Pending transactions do not commit until the Raft leader proposes a block. Enable periodic, non-empty proposals on every validator that may become leader:

dotenv
RAFT_AUTO_BUILD_BLOCKS=true
RAFT_BLOCK_INTERVAL_MS=5000
RAFT_MAX_TRANSACTIONS_PER_BLOCK=100

Each validator may use the same settings. Only the current leader proposes; followers skip the interval tick, observers never start the timer, and a new block is not created when the mempool is empty. The adapter also prevents two interval ticks from proposing concurrently. Production profiles leave this disabled until the operator explicitly selects an interval.

Confirm the live setting through GET /api/consensus/status. The response includes autoBuildBlocks and blockIntervalMs.

Local Services

ServicePurpose
postgresLocal PostgreSQL containing separate validator and observer databases.
migrate-validatorApplies validator database migrations and exits.
validatorHashiCorp validator with Nervos and Wasmtime available locally.
migrate-observerOptional observer migration job.
observerOptional non-voting observer.
cliOn-demand operator and maintenance CLI.

The default stack publishes only the validator NOOSChain API on host port 9001. HashiCorp sidecar HTTP and Raft TCP ports remain inside the private Compose network.

Start The Observer

powershell
docker compose -f docker/compose.local.yaml --profile observer up -d

The observer does not bootstrap a second cluster. It starts with HASHICORP_GO_RAFT_BOOTSTRAP=false and must be added to live runtime membership as a non-voter.

Inspect and save the operator plan:

powershell
docker compose -f docker/compose.local.yaml exec validator `
  noos governance operator-membership plan --json

Review the output, place only approved actions in docker/config/approved-plan.json, and execute the normal guarded workflow through the mounted CLI service:

powershell
docker compose -f docker/compose.local.yaml run --rm cli `
  governance operator-membership execute --plan-file /config/approved-plan.json --dry-run

docker compose -f docker/compose.local.yaml run --rm cli `
  governance operator-membership execute --plan-file /config/approved-plan.json --yes

Files under docker/config are mounted into the on-demand CLI service.

Run The CLI

The image exposes the compiled CLI as noos.

Run against the active validator environment:

powershell
docker compose -f docker/compose.local.yaml exec validator noos node health --json
docker compose -f docker/compose.local.yaml exec validator noos raft-go health --json
docker compose -f docker/compose.local.yaml exec validator noos state-root preflight --json
docker compose -f docker/compose.local.yaml exec validator noos contracts runtime preflight --json

Run an isolated API-based CLI container:

powershell
docker compose -f docker/compose.local.yaml run --rm cli node health --json

Edit or validate a host configuration file through docker/config:

powershell
docker compose -f docker/compose.local.yaml run --rm cli `
  config validate --profile production-validator --file /config/validator.env

Use exec validator for commands that depend on that node's local database or loopback-only sidecars.

Persistence

The local stack creates distinct volumes for:

  • PostgreSQL;
  • validator HashiCorp Raft data;
  • observer HashiCorp Raft data;
  • validator Nervos checkpoints;
  • observer Nervos checkpoints.

Never share Raft or checkpoint volumes between nodes. Nervos checkpoint files are paired with trusted metadata in PostgreSQL. Back up the database and checkpoint volume as related artifacts and run state-root readiness after a restore.

Wasmtime needs no persistent volume. Contract packages and state remain in NOOSChain/PostgreSQL; the child process receives bounded execution inputs over stdin and returns deterministic effects.

Ports And Firewall

The local Compose network uses:

NodeNOOSChain APISidecar HTTPRaft TCP
Validator9001910110001
Observer9002910210002

Only 9001 is published by the default stack. Enabling the observer profile also publishes 9002. Sidecar and Raft ports are exposed only to other containers on the private bridge network.

Nervos SMT and Wasmtime open no ports and need no firewall rules.

For separate production servers:

  • expose the public API through a TLS reverse proxy, normally on 443;
  • keep sidecar HTTP on a management/private network;
  • allow Raft TCP only between approved Raft hosts;
  • keep PostgreSQL private;
  • use reachable private IPs or private DNS names instead of loopback addresses.

Native Sidecar Paths

Docker environment files must use the packaged absolute paths:

dotenv
HASHICORP_GO_RAFT_BINARY_PATH=/usr/local/bin/hashicorp-raft
NOOS_STATE_ROOT_SIDECAR_COMMAND=/usr/local/bin/noos-state-root-sidecar-nervos
NOOS_CONTRACT_WASMTIME_SIDECAR_PATH=/usr/local/bin/noos-contract-executor-wasmtime
NOOS_STATE_ROOT_CHECKPOINT_DIR=/var/lib/nooschain/state-root-checkpoints

Production smart-contract nodes should use:

dotenv
NOOS_SMART_CONTRACTS_ENABLED=true
NOOS_CONTRACT_RUNTIME_PREFLIGHT=required

An incompatible Wasmtime protocol, ABI, executor version, Wasmtime version, host-import set, or fuel policy fails required production preflight. Active Nervos nodes similarly fail closed when the sidecar or checkpoint contract is not ready.

Security Defaults

The local node services use:

  • a non-root user;
  • a read-only root filesystem;
  • dropped Linux capabilities;
  • no-new-privileges;
  • a writable /tmp tmpfs;
  • explicit Raft and checkpoint volumes;
  • no Docker socket mount.

The development examples disable TLS and use a known development operator token. Do not expose them to the public internet.

Production examples under docker/env are incomplete placeholders. Supply real secrets through the deployment secret manager, mount certificates read-only under /run/nooschain/certs, use private addresses, and validate the result before deployment.

Health And Readiness

GET /health is a shallow liveness check. It does not prove database, consensus, or sidecar readiness.

Before routing production traffic, run:

powershell
noos node health --json
noos raft-go health --json
noos state-root readiness --json
noos state-root preflight --json
noos contracts runtime preflight --json
noos production preflight --profile validator

Production Images

Production servers should pull a prebuilt, verified image rather than compile source locally. Use the same Dockerfile and source revision as local testing, but build with version and revision metadata, run smoke gates, publish to a registry, and deploy by immutable digest.

text
registry.example.org/nooschain@sha256:<digest>

Build a multi-architecture OCI archive without publishing it:

powershell
$env:NOOSCHAIN_RELEASE_IMAGE="registry.example.org/nooschain"
$env:NOOSCHAIN_VERSION="0.1.0"
npm run docker:release-build

After verification, publish the same version with BuildKit provenance and SBOM attestations:

powershell
npm run docker:release-push

The release script rejects local and latest as release versions. Signing the published digest and enforcing registry admission policy remain explicit release-operator steps.

Set Compose's image without editing the file:

dotenv
NOOSCHAIN_IMAGE=registry.example.org/nooschain@sha256:<digest>

Roll observers first where possible, run the one-shot migration job, and roll validators one at a time without losing quorum. Verify Raft membership, Nervos readiness, Wasmtime preflight, replay, sync, and chain health after each step.

Smoke Test

The opt-in Docker smoke test builds the image, verifies all native binaries, starts PostgreSQL and the validator, checks API/CLI health, runs Nervos and Wasmtime preflight, generates a temporary development admin identity, initializes genesis, and submits two signed transactions through the packaged CLI. Each transaction is committed through HashiCorp Raft, and the test requires the proposal response to contain the locally applied block before it verifies the transaction as executed. It removes the temporary key material and only its uniquely named Compose project and volumes.

powershell
npm run docker:smoke

The smoke test requires Docker with network access for base images and build dependencies. It is intentionally separate from the fast TypeScript test suite.

Two-Validator Transaction Test

Run the isolated two-voter HashiCorp Raft test:

powershell
npm run docker:test-two-validators

This test builds the image and starts:

  • PostgreSQL with separate databases for validator-a and validator-b;
  • two validator containers with separate Raft and Nervos checkpoint volumes;
  • two HashiCorp sidecars bootstrapped with both nodes as voters;
  • on-demand CLI containers connected to each validator.

It generates a temporary development genesis and admin key, waits for a stable leader, and submits ten signed REGISTER_ORGANIZATION transactions through the leader's packaged CLI. Every transaction is committed in its own block. After each proposal, the test waits until both validators report the transaction as executed at the same height.

The final assertions require:

  • exactly two runtime Raft voters;
  • ten executed transactions on both validators;
  • height 10 on both validators;
  • identical latest block hashes and Nervos state roots;
  • successful proposal responses containing the committed transaction.

The test uses a uniquely named Compose project and removes its temporary keys, containers, network, and volumes. It does not modify the ordinary docker:up stack.

Add A Non-Voting Observer

Run the same ten-transaction scenario with an observer:

powershell
npm run docker:test-two-validators-one-observer

This variant uses the same image for three node containers. validator-a and validator-b remain the only voters. The observer receives its own PostgreSQL database, Raft data volume, and Nervos checkpoint volume.

Because a running observer is read-only, the test applies migrations and initializes its genesis state through a one-shot maintenance container before starting observer-a. After startup, the current leader adds it to HashiCorp Raft with AddNonvoter.

The test additionally requires:

  • runtime membership of exactly two voters and one non-voter on all nodes;
  • all ten transactions to execute on the observer;
  • the observer's height, block hash, and state root to match both validators;
  • the observer to reject a block-proposal request.

Audience-first NOOSChain documentation.