Skip to content

Self-host Provon With Node

Get one Provon process running on a VM, bare-metal host, or container platform. The same process serves the Workbench, API, Gateway, OTLP ingest, MCP endpoint, and background loops.

View as Markdown Open the plain-text version of this page.

Read Architecture before adding replicas: default queues and runtime state are local to one data directory.

Prerequisites#

  • Node.js 22 or newer and pnpm 10 for a source deployment; Docker for a container deployment.
  • A durable filesystem for metadata, telemetry, blobs, and queue state.
  • An HTTPS reverse proxy or platform load balancer for public traffic.
  • Outbound access to enabled model providers, identity providers, and connectors.
  • A stable PROVON_AUTH_SECRET of at least 32 random bytes.

Note

Size storage from expected trace volume and payload capture policy, not metadata size. Prompt bodies, tool results, attachments, and retained raw OTLP payloads usually dominate growth.

Choose A Deployment Method#

Build the image locally or pull a published release image from the GitHub Container Registry.

bash
# local build (build target "runtime")
docker build --target runtime -f services/node-server/Dockerfile -t provon:local .

# or pull a release image
docker pull ghcr.io/provon/provon:<version>
docker volume create provon-data

Use a release tag such as 0.1.0; latest points to the most recent release. Images are published for linux/amd64 and linux/arm64. Generate the secret first:

bash
openssl rand -base64 32

Start one instance on the durable volume:

bash
export PROVON_AUTH_SECRET="replace-with-secret-manager-value"

docker run -d \
  --name provon \
  --restart unless-stopped \
  -p 127.0.0.1:3000:3000 \
  -v provon-data:/data \
  -e NODE_ENV=production \
  -e PROVON_AUTH_SECRET \
  -e PROVON_HTTP_HOST=0.0.0.0 \
  -e PROVON_HTTP_PORT=3000 \
  -e PROVON_META_DB_URL=libsql:/data/meta.db \
  -e PROVON_METERING_DB_URL=libsql:/data/metering.db \
  -e PROVON_JOBS_DB_URL=libsql:/data/jobs.db \
  -e PROVON_GATEWAY_DB_URL=libsql:/data/gateway.db \
  -e PROVON_TELEMETRY_DB_URL=duckdb:/data/telemetry.duckdb \
  -e PROVON_BLOBS_DIR=/data/blobs \
  -e PROVON_AUTH_ORIGIN=https://provon.example.com \
  -e PROVON_AUTH_TRUST_HOST=true \
  -e PROVON_WORKBENCH_ORIGINS=https://provon.example.com \
  provon:local

Keep the host port on loopback when a reverse proxy runs on the same host; on a container platform expose port 3000 only through the platform ingress. The image runs as uid and gid 1001; make bind-mount directories writable by that identity before starting.

Telemetry Storage Choice#

Option When to use Notes
Local DuckDB (duckdb: URL) Simplest fully local deployment Keep the file on a local durable disk, not an eventually consistent network filesystem; one process owns the file
Remote libSQL for metadata Centralized metadata behind a managed SQLite service Set the matching *_DB_URL and *_DB_AUTH_TOKEN; telemetry still follows the Node DuckDB backend
bash
PROVON_TELEMETRY_DB_URL=duckdb:/srv/provon/telemetry.duckdb

Move to the Cloudflare runtime when telemetry must use shared object storage or scale independently from the application process.

Reverse Proxy#

Keep Workbench, API, Gateway, OTLP, and the MCP endpoint on one public origin:

text
https://provon.example.com/
https://provon.example.com/auth/*
https://provon.example.com/v1/*
https://provon.example.com/gateway/v1/*
https://provon.example.com/mcp/v1

The proxy must terminate HTTPS, preserve request bodies and streaming responses, support WebSocket upgrades for realtime Gateway paths, allow the configured OTLP body ceiling, and use timeouts suitable for long model responses. Forwarded header handling on Node:

  • set PROVON_TRUST_PROXY_HEADERS=true when a trusted proxy writes X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port; the runtime otherwise strips client-supplied forwarded headers;
  • PROVON_AUTH_TRUST_HOST=true follows the same trust decision; PROVON_AUTH_ORIGIN must be the exact public origin, and PROVON_WORKBENCH_ORIGINS must list exact browser origins without wildcards.

Sign-in and Connector OAuth callbacks use the public origin:

text
https://provon.example.com/auth/callback/github
https://provon.example.com/auth/callback/google
https://provon.example.com/v1/integrations/github/callback

Verify The Deployment#

Check runtime liveness:

bash
curl --fail --silent https://provon.example.com/healthz

The JSON response includes status, version, and deploymentTarget. It proves the HTTP runtime is alive; it does not query every storage or provider dependency.

Then verify the product path:

  1. Sign in and create an organization and project.
  2. Create a project API key.
  3. Send one OTLP trace and confirm it appears in Traces.
  4. Send one Gateway request and confirm both the response and the provider-attempt trace.
  5. Run one diagnostic Rule and inspect its Run.
  6. If enabled, connect GitHub and create one repair work item from a supported Finding.
  7. Restart the process and confirm metadata, telemetry, blobs, and pending work remain available.

Use the Quickstart, Tracing quickstart, and Gateway quickstart for request examples.

Troubleshooting#

Symptom Check Action
Container exits immediately PROVON_AUTH_SECRET missing; data directory not writable by uid/gid 1001 Provide the secret; chown the volume or bind mount
Callback URL uses http or the wrong host PROVON_AUTH_ORIGIN mismatch; forwarded headers untrusted Set the exact HTTPS origin; enable PROVON_TRUST_PROXY_HEADERS behind a trusted proxy
DuckDB errors or file locking Two processes on one file; database on a network filesystem Run one Node owner; move the file to local durable disk
Startup stops on a migration error New code against an incompatible older schema Restore the pre-upgrade data backup and run the matching release; see Upgrading
OTLP returns 2xx but no trace appears Background ingest loop lag or writer error Inspect [ingest-worker] logs and queue state; see Operations

Scale And Availability#

The default Node deployment favors operational simplicity over horizontal availability: run one active process, use the host or platform restart policy, place all local state on durable storage, and back up metadata, telemetry, blobs, and runtime state as one recovery set. Starting more default Node replicas creates independent local work queues and schedulers. When one process no longer meets availability or throughput objectives, replace local adapters with shared infrastructure or move to the Cloudflare runtime.