Skip to content
Tenzro
Documentation menu
Build and operate

Run a node in Docker

Container images of tenzro-node for CPU, CUDA and Vulkan: the TPM passthrough, GPUs, ports, volumes, health check and a systemd service.

Every testnet v1 release publishes container images of tenzro-node next to the release archives. Each image carries the binaries of the matching archive, unchanged, on a minimal base image.

TagPlatformsBaseServes models on
tenzro-node:testnet-v1-cpulinux/amd64, linux/arm64Debian 12 slimthe CPU (CPU-tier models)
tenzro-node:testnet-v1-cudalinux/amd64, linux/arm64NVIDIA CUDA runtime, Ubuntu 24.04NVIDIA GPUs (llama.cpp and ONNX models)
tenzro-node:testnet-v1-vulkanlinux/amd64, linux/arm64Debian 12 slim, Vulkan loader, Mesa Vulkan driversAMD, Intel and NVIDIA GPUs through Vulkan (llama.cpp models)

Each tag also exists pinned to the release commit: tenzro-node:testnet-v1-<commit>-<flavour>. Use the pinned tag in anything you deploy, so a host always runs the build you checked.

The Vulkan image serves llama.cpp models on the GPU. ONNX Runtime has no Vulkan provider, so on an NVIDIA or AMD GPU it refuses ONNX models (embeddings, vision, audio and the other ONNX runtimes) and names the CUDA or ROCm build that serves them. A GPU image never falls back to the CPU: on a host where it finds no usable GPU it stops at startup and names the CPU image.

What the host needs

  • Linux with a TPM 2.0 at /dev/tpmrm0. The node's identity and keys live in the TPM. The container refuses to start without it; it never generates a software key instead.
  • The TPM service credential tenzro-device-key-root, which opens the node's key derivation root. It is sealed to the host TPM with systemd-creds and decrypted by systemd when the node's service starts.
  • For cuda: the NVIDIA driver and the NVIDIA Container Toolkit.
  • For vulkan: on AMD and Intel, the kernel GPU driver (/dev/dri); the image brings the Mesa Vulkan drivers. On NVIDIA, the NVIDIA driver and the NVIDIA Container Toolkit, which mounts the driver's Vulkan support.

Ports and volumes

The image runs as the unprivileged user tenzro (uid 10001, gid 10001).

WhatWhereNotes
Data volume/data/tenzroChain state, models and the wrapped TPM key blobs (ciphertext under this host's TPM, useless anywhere else). Mount a named volume or a directory owned by uid 10001.
Service credential$CREDENTIALS_DIRECTORY/tenzro-device-key-rootMounted read-only from the host service's credential directory, see below.
P2PUDP 9001Outbound is enough; inbound is optional. See Node networking.
JSON-RPCTCP 8545, loopbackNever open it to the network; put your own reverse proxy in front.
Web API and healthTCP 8080, loopbackGET /health answers 200 while the node is up.
MCPTCP 3001, loopback
A2ATCP 3002, loopback

The node binds its APIs to loopback only. Run the container with --network host so they are on the host's loopback, where your reverse proxy reaches them, and the P2P endpoint uses the host's UDP port directly.

The image's health check calls the node's own /health endpoint on port 8080 every 30 seconds; docker ps shows the result.

One-time setup

Create a host user with the image's uid that may use Docker and the TPM, and seal the service credential to the TPM:

bash
sudo useradd --system --uid 10001 --user-group --create-home tenzro
sudo usermod -aG docker,"$(stat -c %G /dev/tpmrm0)" tenzro
sudo install -d -m 0700 /etc/credstore.encrypted
head -c 32 /dev/urandom | sudo systemd-creds encrypt --with-key=tpm2 \
    --name=tenzro-device-key-root - /etc/credstore.encrypted/tenzro-device-key-root
sudo install -d -o 10001 -g 10001 -m 0700 /srv/tenzro

The service

systemd decrypts the credential when the service starts and exposes it, owned by the service user, in $CREDENTIALS_DIRECTORY; the unit mounts that directory read-only into the container. In a unit file $$ passes a $ to the shell. Save this as /etc/systemd/system/tenzro-node.service, choosing the image line for your hardware:

ini
[Unit]
Description=Tenzro node
After=docker.service
Requires=docker.service

[Service]
User=tenzro
LoadCredentialEncrypted=tenzro-device-key-root:/etc/credstore.encrypted/tenzro-device-key-root
Environment=IMAGE=tenzro-node:testnet-v1-<commit>-cpu
# The group id of /dev/tpmrm0 (stat -c %g /dev/tpmrm0)
Environment=TPM_GID=<gid>
# NVIDIA:            Environment=GPU=--gpus all
# AMD or Intel:      Environment=GPU=--device /dev/dri --group-add <gid of /dev/dri/renderD128>
# Vulkan on NVIDIA:  Environment=GPU=--gpus all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics
Environment=GPU=
ExecStartPre=-/usr/bin/docker rm -f tenzro-node
ExecStart=/bin/sh -c 'exec /usr/bin/docker run --name tenzro-node --rm --network host \
    --device /dev/tpmrm0 --group-add $$TPM_GID $$GPU \
    --mount type=bind,source="$$CREDENTIALS_DIRECTORY",target=/run/credentials/tenzro-node,readonly \
    -e CREDENTIALS_DIRECTORY=/run/credentials/tenzro-node \
    -v /srv/tenzro:/data/tenzro \
    "$$IMAGE" --roles validator,storage'
ExecStop=/usr/bin/docker stop tenzro-node
Restart=on-failure

[Install]
WantedBy=multi-user.target

Create the node's TPM keys once, with the same credential and mounts, before the first start (systemd-run expands $$ the same way a unit does):

bash
sudo systemd-run --pipe --wait -p User=tenzro \
    -p LoadCredentialEncrypted=tenzro-device-key-root:/etc/credstore.encrypted/tenzro-device-key-root \
    /bin/sh -c 'docker run --rm --network none \
        --device /dev/tpmrm0 --group-add '"$(stat -c %g /dev/tpmrm0)"' \
        --mount type=bind,source="$$CREDENTIALS_DIRECTORY",target=/run/credentials/tenzro-node,readonly \
        -e CREDENTIALS_DIRECTORY=/run/credentials/tenzro-node \
        -v /srv/tenzro:/data/tenzro \
        tenzro-node:testnet-v1-<commit>-cpu tpm provision'
sudo systemctl daemon-reload
sudo systemctl enable --now tenzro-node

Check the hardware first

hardware needs no TPM. It prints the build, the GPUs it can drive, the ONNX Runtime providers it will use, and whether the node would start:

bash
docker run --rm tenzro-node:testnet-v1-cpu hardware
docker run --rm --gpus all tenzro-node:testnet-v1-cuda hardware
docker run --rm --device /dev/dri tenzro-node:testnet-v1-vulkan hardware

A Vulkan image on a host with a GPU prints build: Vulkan, gpus: 1 (or more) and startup: ok. Set GGML_VK_VISIBLE_DEVICES to a comma-separated list of device indices to choose GPUs; an empty value hides every GPU.

When the container stops at once

MessageFix
no TPM: /dev/tpmrm0 is not in this containerAdd --device /dev/tpmrm0.
the TPM at /dev/tpmrm0 is not readable and writableAdd --group-add "$(stat -c %g /dev/tpmrm0)".
the TPM derivation root needs the service credentialStart it from the service above, which provides CREDENTIALS_DIRECTORY.
this is the Vulkan build of tenzro-node, but no usable Vulkan GPU or driver was foundPass the GPU (--device /dev/dri, or --gpus all on NVIDIA), or run the CPU image.
this is the CUDA build of tenzro-node, but no usable CUDA GPU or driver was foundAdd --gpus all and install the NVIDIA Container Toolkit, or run the CPU image.