# Self-host Provon With Node

The Node runtime is the default self-hosting path. One process serves the Workbench, API, Gateway,
OTLP ingest, background loops, and optional local model runtime.

Use it for a VM, bare-metal host, Docker host, private cloud, or a single-instance container
platform. Read [Self-hosting architecture](./self-hosting-architecture.md) before adding replicas:
the default queues and runtime state are local to one data directory.

## Requirements

- Node.js 22 or newer and pnpm 10 for a source deployment, or Docker for a container deployment.
- A durable filesystem for metadata, local telemetry, blobs, queue state, and model artifacts.
- An HTTPS reverse proxy or platform load balancer for public traffic.
- Outbound access to enabled model providers, identity providers, and connectors.
- A stable production `AUTH_SECRET`.

For production, allocate storage from expected trace volume and payload capture policy rather than
from metadata size. Prompt bodies, tool results, attachments, and retained raw OTLP payloads usually
dominate growth.

## Option A: Docker

Build the checked-in production image:

```bash
docker build --target runtime -t provon:local .
docker volume create provon-data
```

Generate and store a secret in your secret manager:

```bash
openssl rand -base64 32
```

Start one instance with local SQLite, DuckDB, blobs, and runtime state on the durable volume:

```bash
export 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 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_TELEMETRY_DB_URL=duckdb:/data/telemetry.duckdb \
  -e PROVON_LOCAL_BLOB_DIR=/data/blobs \
  -e PROVON_SELF_HOSTED_DIR=/data/self-hosted \
  -e PROVON_AUTH_TRUST_HOST=true \
  -e PROVON_WORKBENCH_ORIGINS=https://provon.example.com \
  provon:local
```

Keep the host port bound to 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`. If you replace the named volume with a bind mount, make its
directories writable by that identity before starting the container.

### About Docker Compose

The checked-in `docker-compose.yml` is a development and integration topology. It:

- builds the same Node image;
- uses MotherDuck for telemetry;
- starts the example TypeScript agent;
- must be adapted to the selected MotherDuck isolation mode and credentials.

Do not treat it as a production manifest without removing the example workload, defining secret
delivery, adding durable backup policy, and configuring the public origin. For shared MotherDuck,
set `PROVON_TELEMETRY_TENANT_ISOLATION=shared` in the service environment and provide
`MOTHERDUCK_TOKEN`; organization isolation requires `MOTHERDUCK_ADMIN_TOKEN`.

## Option B: Run From Source

Install and build:

```bash
pnpm install --frozen-lockfile
pnpm --filter @provon/node-server build
```

Set production configuration:

```bash
export NODE_ENV=production
export 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_TELEMETRY_DB_URL="duckdb:/srv/provon/telemetry.duckdb"
export PROVON_LOCAL_BLOB_DIR="/srv/provon/blobs"
export PROVON_SELF_HOSTED_DIR="/srv/provon/self-hosted"
export PROVON_AUTH_TRUST_HOST=true
export PROVON_WORKBENCH_ORIGINS="https://provon.example.com"
```

Start the deployable entrypoint:

```bash
pnpm --filter @provon/node-server start
```

Use a process supervisor that:

- restarts the process after failure;
- sends `SIGTERM` for graceful shutdown;
- allows enough time for HTTP connections and background loops to stop;
- captures stdout and stderr;
- starts only one active instance for the data directory.

The server applies metadata migrations before it begins listening. A migration failure stops startup
instead of serving the new application against an old schema.

## Choose Telemetry Storage

### Local DuckDB

```bash
PROVON_TELEMETRY_DB_URL=duckdb:/srv/provon/telemetry.duckdb
```

Choose DuckDB for the simplest fully local deployment. Keep the database on a local durable disk,
not an eventually consistent network filesystem. One Node process should own the local file.

### MotherDuck

```bash
PROVON_TELEMETRY_DB_URL=motherduck:provon-telemetry
MOTHERDUCK_TOKEN=...
```

Choose MotherDuck when telemetry should use managed remote storage while the Provon runtime remains
self-hosted. Metadata, blobs, and local queue state still require durable Node storage.

Organization-isolated MotherDuck provisioning uses `MOTHERDUCK_ADMIN_TOKEN`; shared mode uses
`MOTHERDUCK_TOKEN`. Review the complete variables in
[Self-hosting configuration](./self-hosting-configuration.md).

## Configure The Reverse Proxy

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

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

The proxy must:

- terminate HTTPS;
- preserve request bodies and streaming responses;
- support WebSocket upgrades for realtime Gateway paths;
- set `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port`;
- allow the configured OTLP body-size ceiling;
- use timeouts suitable for long model responses.

Set `PROVON_AUTH_TRUST_HOST=true` only when the proxy overwrites forwarded headers and untrusted
clients cannot inject them. Set `PROVON_WORKBENCH_ORIGINS` to the exact browser origin.

OAuth callbacks use the public origin:

```text
https://provon.example.com/api/auth/callback/github
https://provon.example.com/api/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 response includes `status`, `version`, and `deploymentTarget`. This endpoint 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 provider-attempt trace.
5. Run one diagnostic Rule and inspect the Run.
6. If enabled, connect GitHub and create one repair Issue from a supported Finding.
7. Restart the process and confirm metadata, telemetry, blobs, and pending work remain available.

Use the [Quickstart](./quickstart.md), [Tracing quickstart](./tracing-quickstart.md), and
[Gateway quickstart](./gateway-quickstart.md) for the request examples.

## Scale And Availability

The default Node deployment favors operational simplicity over horizontal availability.

- Run one active process.
- Use host or platform restart policy for process recovery.
- Put all local state on durable storage.
- Back up metadata, telemetry, blobs, and runtime state as one recovery set.
- Move telemetry to MotherDuck only when remote telemetry storage is useful; it does not externalize
  local queues.
- Choose the [Cloudflare runtime](./self-hosting-cloudflare.md) when Gateway, ingest, and jobs must
  scale independently.

## Next Steps

- [Configuration](./self-hosting-configuration.md)
- [Security](./self-hosting-security.md)
- [Operations](./self-hosting-operations.md)
- [Troubleshooting](./troubleshooting.md)
