# 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 model runtime.

Use it for a VM, bare-metal host, Docker host, private cloud, or a single-instance container
platform. Read [Self-hosting architecture](./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 -f services/node-server/Dockerfile -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_METERING_DB_URL=libsql:/data/metering.db \
  -e PROVON_TELEMETRY_DB_URL=duckdb:/data/telemetry.duckdb \
  -e PROVON_BLOBS_DIR=/data/blobs \
  -e PROVON_MODELS_DIR=/data/models \
  -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 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 a local DuckDB file for telemetry by default;
- starts the example TypeScript agent;
- must be given a production-safe `AUTH_SECRET`;
- can be adapted to use MotherDuck by setting `PROVON_TELEMETRY_DB_URL` and the
  required MotherDuck 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`. The checked-in
Compose file intentionally does not store `AUTH_SECRET`; provide it through your environment or
secret manager before starting the production image.

## 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_METERING_DB_URL="libsql:/srv/provon/metering.db"
export PROVON_TELEMETRY_DB_URL="duckdb:/srv/provon/telemetry.duckdb"
export PROVON_BLOBS_DIR="/srv/provon/blobs"
export PROVON_MODELS_DIR="/srv/provon/models"
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:

```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](./configuration.md).

## Optional Model Services

The Node runtime can optionally control two standalone Python services for local model execution and
training:

- `services/python-inference` serves text-generation models through an OpenAI-compatible HTTP API.
- `services/python-fine-tuning` trains adapters and exports checkpoints through HTTP.

Both services are started independently. Node communicates with them over HTTP and does not spawn
Python child processes. Point Node at each service through the runtime configuration; see
[Self-hosting configuration](./configuration.md) for the relevant variables.

To serve a self-hosted model:

1. Start `services/python-inference` on a GPU-equipped host.
2. Configure Node with the inference service URL and shared API key.
3. Import a model, install the engine, download the profile, and start the service through the
   [Model Runtime API](../ai-models/runtime.md).
4. Route Gateway requests to `self/<model-id>`.

To run fine-tuning jobs:

1. Start `services/python-fine-tuning` on a GPU-equipped host.
2. Configure Node with the fine-tuning service URL and shared API key.
3. Create a chat Dataset and submit a job through the
   [Model Fine-Tuning API](../ai-models/fine-tuning.md).
4. Deploy the resulting checkpoint to the model runtime.

Both services must see the configured training work directory at the same absolute path as Node,
because fine-tuning requests pass filesystem paths for the Dataset manifest and checkpoint output.

## 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](../get-started/quickstart.md), [Tracing quickstart](../tracing/quickstart.md), and
[Gateway quickstart](../ai-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](./cloudflare.md) when Gateway, ingest, and jobs must
  scale independently.

## Next Steps

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