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.
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_SECRETof 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.
# 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-dataUse 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:
openssl rand -base64 32Start one instance on the durable volume:
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:localKeep 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.
Use the public installer on Linux or macOS to download a release archive and configure a
server environment. It extracts the CLI and Node server, generates a random PROVON_AUTH_SECRET,
and writes an env file under the install prefix (default ~/.provon).
curl -fsSL https://provon.dev/install.sh | shCommon options:
| Option | Purpose | Default |
|---|---|---|
--version VERSION |
Release to install | Latest GitHub release |
--prefix PATH |
Install prefix | ~/.provon |
--data-dir PATH |
Persistent data directory | <prefix>/data |
--daemon |
Install and start a systemd user unit (Linux) or launchd agent (macOS) | Off |
--dry-run |
Print actions without executing | Off |
With --daemon, Linux installs a user systemd unit; run loginctl enable-linger <user> so
it survives logout. macOS installs a launchd agent. After install, edit the generated env
file (<prefix>/env, mode 600) to set PROVON_AUTH_ORIGIN, PROVON_WORKBENCH_ORIGINS, and
production storage paths, then reload the service. Windows uses the PowerShell installer
install.ps1; see Connectors and runtime variables for the full list.
Install dependencies and build the runtime plus Workbench:
pnpm install --frozen-lockfile
pnpm --filter @provon/node-server buildSet production configuration before starting:
export NODE_ENV=production
export PROVON_AUTH_SECRET="replace-with-secret-manager-value"
export PROVON_HTTP_HOST=127.0.0.1
export PROVON_HTTP_PORT=3000
export PROVON_META_DB_URL="libsql:/srv/provon/meta.db"
export PROVON_METERING_DB_URL="libsql:/srv/provon/metering.db"
export PROVON_JOBS_DB_URL="libsql:/srv/provon/jobs.db"
export PROVON_GATEWAY_DB_URL="libsql:/srv/provon/gateway.db"
export PROVON_TELEMETRY_DB_URL="duckdb:/srv/provon/telemetry.duckdb"
export PROVON_BLOBS_DIR="/srv/provon/blobs"
export PROVON_AUTH_ORIGIN="https://provon.example.com"
export PROVON_AUTH_TRUST_HOST=true
export PROVON_WORKBENCH_ORIGINS="https://provon.example.com"Start the deployable entrypoint:
pnpm --filter @provon/node-server startUse a process supervisor that restarts the process after failure, sends SIGTERM for
graceful shutdown, allows time for HTTP connections and background loops to drain, captures
stdout and stderr, and starts only one active instance per data directory. The server
applies store migrations before it begins listening; a migration failure stops startup
instead of serving against an old schema.
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 |
PROVON_TELEMETRY_DB_URL=duckdb:/srv/provon/telemetry.duckdbMove 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:
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/v1The 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=truewhen a trusted proxy writesX-Forwarded-Proto,X-Forwarded-Host, andX-Forwarded-Port; the runtime otherwise strips client-supplied forwarded headers; PROVON_AUTH_TRUST_HOST=truefollows the same trust decision;PROVON_AUTH_ORIGINmust be the exact public origin, andPROVON_WORKBENCH_ORIGINSmust list exact browser origins without wildcards.
Sign-in and Connector OAuth callbacks use the public origin:
https://provon.example.com/auth/callback/github
https://provon.example.com/auth/callback/google
https://provon.example.com/v1/integrations/github/callbackVerify The Deployment#
Check runtime liveness:
curl --fail --silent https://provon.example.com/healthzThe 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:
- Sign in and create an organization and project.
- Create a project API key.
- Send one OTLP trace and confirm it appears in Traces.
- Send one Gateway request and confirm both the response and the provider-attempt trace.
- Run one diagnostic Rule and inspect its Run.
- If enabled, connect GitHub and create one repair work item from a supported Finding.
- 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.