---
title: "Self-hosted data plane (BYOC)"
description: "Run the PgBeam proxy inside your own VPC or cluster so the agent connection and audit stream never leave your network, while the control plane, policy engine, and dashboard stay hosted by PgBeam."
canonical: "https://pgbeam.com/docs/self-hosted"
last-updated: "2026-10-15T11:54:41.000Z"
---

# Self-hosted data plane (BYOC)

> Run the PgBeam proxy inside your own VPC or cluster so the agent connection and audit stream never leave your network, while the control plane, policy engine, and dashboard stay hosted by PgBeam.

URL: https://pgbeam.com/docs/self-hosted

The self-hosted (BYOC, "bring your own cloud") data plane runs the same PgBeam proxy image inside your own network. Queries from your agents flow through a proxy you operate; the proxy only dials home to the PgBeam control plane over gRPC to fetch project config and to ship the audit trail back. Your database credentials and query traffic stay in your VPC.

This is a Scale and enterprise capability. It sells the compliance story: your data plane, our policy engine.

## What stays where

| Component                       | Runs where                  |
| ------------------------------- | --------------------------- |
| Proxy (PG wire, policy, cache)  | Your VPC/cluster            |
| Query traffic + DB credentials  | Your VPC/cluster            |
| Control plane (config, billing) | PgBeam (hosted)             |
| Dashboard, policy authoring     | PgBeam (hosted)             |
| Audit trail                     | Shipped to PgBeam over gRPC |

The proxy enforces every policy (read-only, allowlists, masking, budgets, approvals, kill-switch) locally in the PG wire protocol. It receives that policy from the control plane over the config stream, the same mechanism the hosted data plane uses.

## How enrollment works

A self-hosted proxy authenticates to the control plane with a self-host enrollment token. The token:

* Is issued once per enrollment and shown a single time. Only its SHA-256 hash is stored.
* Scopes the proxy to your organization. A self-hosted proxy receives config for your projects only, never another tenant's, and never the PgBeam platform TLS key.
* Is gated on entitlement. If your organization is not on a self-host-capable plan (Scale or enterprise), the connection is rejected.
* Can expire, be rotated, and be revoked. All three take effect immediately, including for proxies that are already connected. See the [token lifecycle](#token-lifecycle) below.

Because the proxy runs in your network, you provide its TLS certificate for your proxy domain. The platform wildcard certificate is never streamed to a self-hosted proxy.

## Issue an enrollment token

Create an enrollment for your organization (owner or admin):

```bash
curl -X POST https://api.pgbeam.com/v1/organizations/$ORG_ID/self-host-enrollments \
  -H "Authorization: Bearer $PGBEAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"region_label": "customer-vpc-us-east", "description": "prod cluster", "expires_at": "2027-01-01T00:00:00Z"}'
```

`expires_at` is optional; omit it for a token that never expires. Setting it gives the token a hard TTL, which keeps the blast radius of a leak small.

The response includes the plaintext token once:

```json
{
  "enrollment": {
    "id": "she_...",
    "org_id": "org_...",
    "region_label": "customer-vpc-us-east"
  },
  "token": "pbh_..."
}
```

Store the `token` securely; it cannot be retrieved again. List enrollments with `GET` on the same path.

## Token lifecycle

Treat an enrollment token like any other production secret, and use expiry and rotation to limit how long a leaked token stays useful.

* **Expiry**: an expired token is rejected fail-closed at the control plane's gRPC auth gate the instant its `expires_at` passes. Expiry is visible in the list endpoint and the dashboard.
* **Rotation**: `POST /v1/organizations/{org_id}/self-host-enrollments/{enrollment_id}/rotate` mints a new token for the same enrollment (same id, metadata, and expiry) and returns it once. The swap is atomic: the old token stops authenticating new connections the moment the call returns. Update the proxy's secret (`controlPlane.enrollmentToken` in Helm, `PGBEAM_ENROLLMENT_TOKEN` in compose) and restart or roll the proxy to pick up the new token.
* **Revocation**: `DELETE .../self-host-enrollments/{enrollment_id}` cuts the enrollment off permanently. Revoked enrollments cannot be rotated; issue a new enrollment instead.

Expiry, rotation, and revocation stop new gRPC connections and also terminate the streams a connected proxy already holds. On revoke or rotate, the control plane closes the proxy's live streams the moment the change commits; a stream opened before an expiry instant is closed when that instant passes. The proxy sees a `permission_denied` status naming the enrollment and the reason in its logs, then reconnects with backoff and fails auth until it presents a currently valid token. Rotate first, roll the proxy with the new token, and the old token is dead from the moment of rotation; there is no shared-secret overlap window to manage.

## Mark a project self-hosted

Set `self_hosted` when creating a project so the control plane treats its data plane as customer-operated and does not provision hosted infrastructure for it:

```bash
curl -X POST https://api.pgbeam.com/v1/projects \
  -H "Authorization: Bearer $PGBEAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "org_id": "org_...", "self_hosted": true, "database": { "host": "db.internal", "name": "app", "username": "app" }}'
```

## Run the proxy

Packaging for both Kubernetes and Docker Compose lives in `deploy/byoc/`.

### Helm

```bash
helm install pgbeam-proxy ./deploy/byoc/helm/pgbeam-proxy \
  --set controlPlane.url=https://grpc.pgbeam.com \
  --set controlPlane.enrollmentToken=pbh_... \
  --set proxy.domain=proxy.internal.example.com \
  --set-file tls.cert=./tls.crt \
  --set-file tls.key=./tls.key
```

### Docker Compose

```bash
cd deploy/byoc
cp .env.example .env      # fill in PGBEAM_CONTROL_PLANE_URL and PGBEAM_ENROLLMENT_TOKEN
docker compose up -d
```

## Connect your agents

Route clients through the proxy using TLS SNI, exactly as with the hosted data plane, but against your proxy domain:

```
postgresql://user:pass@<project-subdomain>.proxy.internal.example.com:5432/db?sslmode=verify-full
```

The subdomain identifies the project. The proxy resolves it against the config streamed from the control plane.

## Configuration reference

The proxy is configured entirely with environment variables. The most relevant for BYOC:

| Variable          | Purpose                                                       |
| ----------------- | ------------------------------------------------------------- |
| `AGENT_API_URL`   | Control-plane gRPC URL to dial home to                        |
| `GRPC_AUTH_TOKEN` | Self-host enrollment token (pbh\_...)                         |
| `AGENT_REGION`    | Region label reported to the control plane                    |
| `AGENT_INSTANCE`  | Unique instance id for this proxy                             |
| `PROXY_DOMAIN`    | Base domain for TLS SNI project routing                       |
| `TLS_CERT_FILE`   | Path to your proxy TLS certificate                            |
| `TLS_KEY_FILE`    | Path to your proxy TLS private key                            |
| `SERVERLESS_ADDR` | Listen address for the Neon-compatible HTTP/WS + MCP endpoint |
| `CACHE_ENABLED`   | Enable query caching (off by default)                         |

## Limits and follow-ups

* Config, audit, and hostname self-heal are scoped to the enrolling org. Metrics and insights reporting from a self-hosted proxy are trusted at the org level; fine-grained per-project scoping of those aggregate reports is a planned follow-up.