Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Docker

Nervix publishes a rolling multi-architecture Debian image to ghcr.io/nervix-io/nervix:debian-latest. The image contains nervix-server, nervix-cli, and nervix-nspl-format. The examples on this page create a three-node cluster for local evaluation.

For a reproducible or production deployment, replace the rolling tag with a reviewed timestamped tag or image digest. Use TLS, a secret manager, and a client-reachable advertise hostname instead of the local-development defaults shown here.

CUDA Images

Build a local CUDA image from a repository checkout with just docker-build-cuda; the build requirements are in Developing Nervix. The image uses the cuda target in Dockerfile.debian; the CPU image uses the cpu target. Both copy the same Nervix binaries from one shared builder stage. The CUDA image contains ONNX Runtime 1.30.0’s CUDA 13 provider, CUDA 13.2.2, and cuDNN 9.25.1. It supports Linux AMD64 and pins the Debian base to debian:trixie-20260918-slim and its image digest. CUDA and cuDNN occupy one layer directly above that base. The next layer updates and upgrades Debian packages while holding CUDA and cuDNN at their pinned versions. Nervix’s layers follow and reuse that CUDA layer across application builds.

To expose an NVIDIA GPU, install a host driver supporting CUDA 13.2 and the NVIDIA Container Toolkit, set NERVIX_IMAGE=nervix:cuda, and add --gpus all to the docker run commands below. The container toolkit supplies the host driver; CUDA and cuDNN are supplied by the image.

Run Three Nodes With Docker

Choose the image and initial password. The password environment variable is expanded by the shell and passed only to the bootstrap node, which creates the default user with it. The other nodes need no password: they read that user from the cluster, and a node that stops gracefully asks the leader to drain it under its node certificate.

export NERVIX_IMAGE=ghcr.io/nervix-io/nervix:debian-latest
export NERVIX_INIT_DEFAULT_USER_PASSWORD='replace-with-a-password'

docker network create nervix
docker pull "$NERVIX_IMAGE"

Provision a CA and one certificate/key pair per node under ./tls before starting the containers:

tls/ca.pem
tls/node-1.pem
tls/node-1-key.pem
tls/node-2.pem
tls/node-2-key.pem
tls/node-3.pem
tls/node-3-key.pem

Each node certificate must permit TLS server and client authentication. It must contain its Docker hostname (nervix-1, nervix-2, or nervix-3) as a DNS SAN and exactly one identity URI matching its configured cluster and node, such as nervix://cluster/nervix-docker/node/node-1. Sign all three certificates with the CA in ca.pem.

Start the bootstrap node:

docker run --detach \
  --name nervix-1 \
  --hostname nervix-1 \
  --network nervix \
  --restart unless-stopped \
  --stop-timeout 60 \
  --volume nervix-node-1-data:/var/lib/nervix \
  --volume "$PWD/tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro" \
  --volume "$PWD/tls/node-1.pem:/etc/nervix/interconnect/node.pem:ro" \
  --volume "$PWD/tls/node-1-key.pem:/etc/nervix/interconnect/node-key.pem:ro" \
  --publish 47391:47391 \
  --publish 47421:47420 \
  --publish 9091:9090 \
  --env NERVIX_ADDR=0.0.0.0:47391 \
  --env NERVIX_GRPC_ADVERTISE_ADDR=127.0.0.1:47391 \
  --env NERVIX_WEB_CONSOLE_LISTEN_ADDR=0.0.0.0:47420 \
  --env NERVIX_WEB_CONSOLE_ADVERTISE_ADDR=127.0.0.1:47421 \
  --env NERVIX_OBSERVABILITY_LISTEN_ADDR=0.0.0.0:9090 \
  --env NERVIX_CLUSTER_ID=nervix-docker \
  --env NERVIX_NODE_ID=node-1 \
  --env NERVIX_INTERCONNECT_LISTEN_ADDR=0.0.0.0:47395 \
  --env NERVIX_INTERCONNECT_ADVERTISE_ADDR=nervix-1:47395 \
  --env NERVIX_INTERCONNECT_TLS_CA=/etc/nervix/interconnect/ca.pem \
  --env NERVIX_INTERCONNECT_TLS_CERT=/etc/nervix/interconnect/node.pem \
  --env NERVIX_INTERCONNECT_TLS_KEY=/etc/nervix/interconnect/node-key.pem \
  --env NERVIX_REPLICA_COUNT=3 \
  --env NERVIX_ALLOW_BOOTSTRAP=true \
  --env NERVIX_INIT_DEFAULT_USER_PASSWORD \
  "$NERVIX_IMAGE"

Start the other two nodes. Both discover the cluster through nervix-1 on the private Docker network:

Keep each node’s named data volume across restarts. Once the three nodes have joined, their Raft membership retains the advertised interconnect names. If nervix-1 stops while the other nodes continue, it uses those names to find a survivor when it restarts; it does not need a second configured bootstrap host. The joining nodes still use NERVIX_CLUSTER_BOOTSTRAP_HOST for their first contact with a fresh cluster. Once they have recovered Raft peer endpoints from their volumes, they can restart while that bootstrap host does not resolve and rejoin through another peer whose name resolves. Keep the other members’ advertised names resolvable to the restarting node.

docker run --detach \
  --name nervix-2 \
  --hostname nervix-2 \
  --network nervix \
  --restart unless-stopped \
  --stop-timeout 60 \
  --volume nervix-node-2-data:/var/lib/nervix \
  --volume "$PWD/tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro" \
  --volume "$PWD/tls/node-2.pem:/etc/nervix/interconnect/node.pem:ro" \
  --volume "$PWD/tls/node-2-key.pem:/etc/nervix/interconnect/node-key.pem:ro" \
  --publish 47392:47391 \
  --publish 47422:47420 \
  --publish 9092:9090 \
  --env NERVIX_ADDR=0.0.0.0:47391 \
  --env NERVIX_GRPC_ADVERTISE_ADDR=127.0.0.1:47392 \
  --env NERVIX_WEB_CONSOLE_LISTEN_ADDR=0.0.0.0:47420 \
  --env NERVIX_WEB_CONSOLE_ADVERTISE_ADDR=127.0.0.1:47422 \
  --env NERVIX_OBSERVABILITY_LISTEN_ADDR=0.0.0.0:9090 \
  --env NERVIX_CLUSTER_ID=nervix-docker \
  --env NERVIX_NODE_ID=node-2 \
  --env NERVIX_INTERCONNECT_LISTEN_ADDR=0.0.0.0:47395 \
  --env NERVIX_INTERCONNECT_ADVERTISE_ADDR=nervix-2:47395 \
  --env NERVIX_INTERCONNECT_TLS_CA=/etc/nervix/interconnect/ca.pem \
  --env NERVIX_INTERCONNECT_TLS_CERT=/etc/nervix/interconnect/node.pem \
  --env NERVIX_INTERCONNECT_TLS_KEY=/etc/nervix/interconnect/node-key.pem \
  --env NERVIX_REPLICA_COUNT=3 \
  --env NERVIX_CLUSTER_BOOTSTRAP_HOST=nervix-1:47395 \
  "$NERVIX_IMAGE"

docker run --detach \
  --name nervix-3 \
  --hostname nervix-3 \
  --network nervix \
  --restart unless-stopped \
  --stop-timeout 60 \
  --volume nervix-node-3-data:/var/lib/nervix \
  --volume "$PWD/tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro" \
  --volume "$PWD/tls/node-3.pem:/etc/nervix/interconnect/node.pem:ro" \
  --volume "$PWD/tls/node-3-key.pem:/etc/nervix/interconnect/node-key.pem:ro" \
  --publish 47393:47391 \
  --publish 47423:47420 \
  --publish 9093:9090 \
  --env NERVIX_ADDR=0.0.0.0:47391 \
  --env NERVIX_GRPC_ADVERTISE_ADDR=127.0.0.1:47393 \
  --env NERVIX_WEB_CONSOLE_LISTEN_ADDR=0.0.0.0:47420 \
  --env NERVIX_WEB_CONSOLE_ADVERTISE_ADDR=127.0.0.1:47423 \
  --env NERVIX_OBSERVABILITY_LISTEN_ADDR=0.0.0.0:9090 \
  --env NERVIX_CLUSTER_ID=nervix-docker \
  --env NERVIX_NODE_ID=node-3 \
  --env NERVIX_INTERCONNECT_LISTEN_ADDR=0.0.0.0:47395 \
  --env NERVIX_INTERCONNECT_ADVERTISE_ADDR=nervix-3:47395 \
  --env NERVIX_INTERCONNECT_TLS_CA=/etc/nervix/interconnect/ca.pem \
  --env NERVIX_INTERCONNECT_TLS_CERT=/etc/nervix/interconnect/node.pem \
  --env NERVIX_INTERCONNECT_TLS_KEY=/etc/nervix/interconnect/node-key.pem \
  --env NERVIX_REPLICA_COUNT=3 \
  --env NERVIX_CLUSTER_BOOTSTRAP_HOST=nervix-1:47395 \
  "$NERVIX_IMAGE"

The host ports are:

NodegRPCWeb consoleHealth and metrics
node-147391474219091
node-247392474229092
node-347393474239093

Connect an installed CLI to http://127.0.0.1:47391, or open the web console at http://127.0.0.1:47421/console/, and run:

SHOW CLUSTER STATUS;

The cluster status should show one local node, two live peer nodes, and all three nodes in the Raft membership. The CLI follows leader redirects through the three published gRPC ports.

Each node advertises its container name, such as nervix-1:47395, and finds its peers by those names through Docker’s embedded DNS, which Docker names in the container’s /etc/resolv.conf. A node resolves peer names, and the hosts its connectors reach, with its own asynchronous resolver from that file and /etc/hosts, read once at startup; Name Resolution describes the options that point it at other files or name servers, and what it does not support.

docker stop sends each node SIGTERM, which starts graceful shutdown, and kills a container only if its node is still running when the stop timeout ends. A node ends its own shutdown within its shutdown timeout, 50 seconds by default, which covers the 30-second default drain timeout and the services that stop after it. The --stop-timeout 60 above keeps Docker waiting longer than that, so a node that cannot finish shutting down exits with status 1 on its own rather than being killed; with Docker’s default of ten seconds, a node could be killed partway through its drain. Shutdown And Recovery explains what a node loses when its grace period ends first. Stop the nodes, then remove the containers and private network:

docker stop nervix-1 nervix-2 nervix-3
docker rm nervix-1 nervix-2 nervix-3
docker network rm nervix

The named volumes remain so the cluster can be recreated with its state intact. Removing nervix-node-1-data, nervix-node-2-data, and nervix-node-3-data permanently deletes that state.

Run Three Nodes With Docker Compose

Save the following as docker-compose.yml:

x-nervix-common: &nervix-common
  image: ${NERVIX_IMAGE:-ghcr.io/nervix-io/nervix:debian-latest}
  restart: unless-stopped
  stop_grace_period: 60s
  environment: &nervix-environment
    NERVIX_ADDR: 0.0.0.0:47391
    NERVIX_WEB_CONSOLE_LISTEN_ADDR: 0.0.0.0:47420
    NERVIX_OBSERVABILITY_LISTEN_ADDR: 0.0.0.0:9090
    NERVIX_CLUSTER_ID: nervix-docker
    NERVIX_INTERCONNECT_LISTEN_ADDR: 0.0.0.0:47395
    NERVIX_INTERCONNECT_TLS_CA: /etc/nervix/interconnect/ca.pem
    NERVIX_INTERCONNECT_TLS_CERT: /etc/nervix/interconnect/node.pem
    NERVIX_INTERCONNECT_TLS_KEY: /etc/nervix/interconnect/node-key.pem
    NERVIX_REPLICA_COUNT: "3"
  networks:
    - nervix

services:
  nervix-1:
    <<: *nervix-common
    hostname: nervix-1
    environment:
      <<: *nervix-environment
      NERVIX_NODE_ID: node-1
      NERVIX_GRPC_ADVERTISE_ADDR: 127.0.0.1:47391
      NERVIX_WEB_CONSOLE_ADVERTISE_ADDR: 127.0.0.1:47421
      NERVIX_INTERCONNECT_ADVERTISE_ADDR: nervix-1:47395
      NERVIX_ALLOW_BOOTSTRAP: "true"
      NERVIX_INIT_DEFAULT_USER_PASSWORD: ${NERVIX_INIT_DEFAULT_USER_PASSWORD:?set NERVIX_INIT_DEFAULT_USER_PASSWORD}
    ports:
      - "47391:47391"
      - "47421:47420"
      - "9091:9090"
    volumes:
      - nervix-node-1-data:/var/lib/nervix
      - ./tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro
      - ./tls/node-1.pem:/etc/nervix/interconnect/node.pem:ro
      - ./tls/node-1-key.pem:/etc/nervix/interconnect/node-key.pem:ro

  nervix-2:
    <<: *nervix-common
    hostname: nervix-2
    depends_on:
      - nervix-1
    environment:
      <<: *nervix-environment
      NERVIX_NODE_ID: node-2
      NERVIX_GRPC_ADVERTISE_ADDR: 127.0.0.1:47392
      NERVIX_WEB_CONSOLE_ADVERTISE_ADDR: 127.0.0.1:47422
      NERVIX_INTERCONNECT_ADVERTISE_ADDR: nervix-2:47395
      NERVIX_CLUSTER_BOOTSTRAP_HOST: nervix-1:47395
    ports:
      - "47392:47391"
      - "47422:47420"
      - "9092:9090"
    volumes:
      - nervix-node-2-data:/var/lib/nervix
      - ./tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro
      - ./tls/node-2.pem:/etc/nervix/interconnect/node.pem:ro
      - ./tls/node-2-key.pem:/etc/nervix/interconnect/node-key.pem:ro

  nervix-3:
    <<: *nervix-common
    hostname: nervix-3
    depends_on:
      - nervix-1
    environment:
      <<: *nervix-environment
      NERVIX_NODE_ID: node-3
      NERVIX_GRPC_ADVERTISE_ADDR: 127.0.0.1:47393
      NERVIX_WEB_CONSOLE_ADVERTISE_ADDR: 127.0.0.1:47423
      NERVIX_INTERCONNECT_ADVERTISE_ADDR: nervix-3:47395
      NERVIX_CLUSTER_BOOTSTRAP_HOST: nervix-1:47395
    ports:
      - "47393:47391"
      - "47423:47420"
      - "9093:9090"
    volumes:
      - nervix-node-3-data:/var/lib/nervix
      - ./tls/ca.pem:/etc/nervix/interconnect/ca.pem:ro
      - ./tls/node-3.pem:/etc/nervix/interconnect/node.pem:ro
      - ./tls/node-3-key.pem:/etc/nervix/interconnect/node-key.pem:ro

networks:
  nervix:

volumes:
  nervix-node-1-data:
  nervix-node-2-data:
  nervix-node-3-data:

Set the required initial password and start the cluster from the directory containing that file:

export NERVIX_INIT_DEFAULT_USER_PASSWORD='replace-with-a-password'
docker compose up --detach

Set NERVIX_IMAGE before docker compose up to use a pinned tag or digest instead of debian-latest.

Inspect the containers and follow their logs:

docker compose ps
docker compose logs --follow

Use the same host ports in the table above. Stop the cluster while preserving its volumes:

docker compose down

docker compose down sends each node SIGTERM and waits up to the file’s stop_grace_period of 60 seconds, longer than each node’s 50-second shutdown timeout, for graceful shutdown to finish before it kills a container. To permanently delete all three nodes’ persisted state, add --volumes.