---
title: Self-host Veefore
description: Run the control plane, the gateway and the console on your own infrastructure, against your own PostgreSQL.
label: Self-host Veefore
---

:::note Using hosted Veefore?
You do not need this page. Install the CLI and run `veefore` in your agent
project; see the [Quickstart](/quickstart).
:::

Self-hosting keeps every agent record, policy and audit event inside your own
network. Veefore runs as three Node processes over one PostgreSQL database, and
this page gets all of them running from one command, first on one machine, then
reachable by your engineers. For production deployment, see
[Deployment](/operate/deployment).

## Requirements

| | Version | Why |
| --- | --- | --- |
| Node | 20.10 or newer | Native ESM loader behaviour the packages rely on |
| PostgreSQL | 14 or newer | The shared database for every process |
| Python | 3.9 or newer | Only if you work on the Python SDK |
| Docker | any recent | Only as a convenient way to get PostgreSQL |

## Get it running

:::steps

:::step Install dependencies

```bash
git clone https://github.com/veefore/veefore.git
cd veefore
npm install
```
:::

:::step Start a database

Any reachable PostgreSQL works. If you want one in a container:

```bash
docker run -d --name veefore-postgres \
  -e POSTGRES_USER=veefore \
  -e POSTGRES_PASSWORD=veefore \
  -e POSTGRES_DB=veefore \
  -p 55432:5432 \
  postgres:16
```

Port `55432` rather than `5432` so it cannot collide with a PostgreSQL you
already run.

:::warning Use a real PostgreSQL, not the embedded driver
Veefore ships an embedded PGlite driver, and it is genuinely useful for isolated
tests. It is the wrong choice for running the stack, because it is
**per-process**: the control plane, the gateway and the seed script would each
get their own private copy of the database, and nothing would see anyone else's
writes. The symptom is confusing — an agent you just created does not exist.
:::
:::

:::step Configure

```bash
cp .env.example .env
```

Two things to set, and one to check:

```ini
# Point at the database you just started.
DATABASE_URL=postgres://veefore:veefore@127.0.0.1:55432/veefore

# These ship as the defaults. Change them only if a port is taken.
CONTROL_PLANE_PORT=4000
GATEWAY_PORT=4010
VEEFORE_CONSOLE_PORT=5173
CORS_ALLOWED_ORIGINS=http://localhost:5173
```

`DATABASE_URL` is empty in `.env.example`, and empty means the embedded driver —
see the warning above. Setting it is the one edit you must make.

If you change the console port, change `CORS_ALLOWED_ORIGINS` to match or the
browser will refuse every request with no useful error. Full list in
[Configuration reference](/reference/configuration).

:::security Generate your own keys
`.env.example` ships with development keys so the stack boots. They are shared
with everyone who ever cloned the repository, so they are not secret by
definition. Before anything that matters, replace
`VEEFORE_DATA_ENCRYPTION_KEY`, `VEEFORE_SIGNING_PRIVATE_KEY`,
`VEEFORE_SESSION_SECRET` and `VEEFORE_AUDIT_CHAIN_SECRET`. See
[Secrets](/secure/secrets).
:::
:::

:::step Migrate and seed

```bash
npm run migrate
npm run seed:demo
```

`seed:demo` creates an organization with agents, tools, policies, delegations,
credentials, approvals, incidents and audit history — enough that every console
page has something real to render. The accounts it creates are printed at the
end; the owner is `owner@acme.test` with the password shown in the output.

For an empty organization with no sample data, use `npm run seed` instead.

`npm run migrate:check` applies every migration to a throwaway database and
verifies it comes out clean. That is the one to run in CI.
:::

:::step Start everything

```bash
npm run dev
```

| Process | URL |
| --- | --- |
| Control plane | `http://localhost:4000` |
| Gateway | `http://localhost:4010` |
| Console | `http://localhost:5173` |

Individual processes, if you want them in separate terminals:

```bash
npm run dev:control-plane
npm run dev:gateway
npm run dev:console
```
:::

:::step Confirm it is healthy

```bash
curl -s http://localhost:4000/health/ready
curl -s http://localhost:4010/health/ready
curl -s http://localhost:4000/v1/meta/info
```

`/health/ready` checks the database. If the database is gone, readiness goes
red — it does not report healthy and then fail every request, which would be
the more annoying failure.

`/v1/meta/info` needs no authentication and tells you the version, the
environment, both public URLs, and the published capability and exclusion lists.
:::

:::

## Connect agent projects

Engineers never clone this repository. They install the CLI and the SDK from
the package registries. On its own, `veefore` connects to the hosted Veefore
service, so engineers point it at your deployment the first time:

```bash
npm install -g veefore
cd their-agent-project
veefore --control-plane https://veefore.your-company.com
```

After that the CLI remembers the address, and each project records it in its
committed `veefore.config.json`, so teammates run plain `veefore`. To avoid the
flag entirely, set `VEEFORE_CONTROL_PLANE_URL` on engineers' machines, for
example through your device management. The console's **Developer** page shows
the exact command with your address filled in.

The CLI signs them in through the console in their browser, so three addresses
must be right before you hand out the URL:

| Variable | Set it to | Why |
| --- | --- | --- |
| `CONTROL_PLANE_PUBLIC_URL` | The address engineers pass to `--control-plane` | Published at `/v1/meta/info` |
| `GATEWAY_PUBLIC_URL` | The address agents reach the gateway at | Written into each project's configuration; it is also the signing audience |
| `CONSOLE_PUBLIC_URL` | The address people open the console at | Where browser sign-in for the CLI happens. Defaults to the first `CORS_ALLOWED_ORIGINS` entry |
| `VEEFORE_HOSTED` | `false` | Leave it off for a self-hosted install, so setup instructions include your address |

`veefore` reads the gateway and console addresses from the control plane, so
engineers only ever need the one URL. It also remembers it: later commands in
any project reuse the last address.

:::warning A loopback address only works on the same machine
`http://localhost:4000` is right for trying Veefore on your own laptop and wrong
for anyone else. Before inviting engineers, serve all three behind HTTPS on
addresses their machines and their agents can reach. See
[Run the stack](/operate/run-the-stack).
:::

## Run the documentation locally

These docs are a workspace in the same repository.

```bash
npm run docs:dev      # http://localhost:4800, rebuilds on save
npm run docs:build    # static output in docs/dist
npm run docs:check    # validate without writing anything
```

`docs:check` is the one that matters in CI: it fails on a broken internal link,
a page missing from the navigation, a page in the navigation with no file, an
API group added to the catalogue without an explanation, or an error code with
no documented status group.

## Verify the whole system

```bash
npm run verify
```

This is the single command that proves the system works. Twenty-three stages, in
the order a reviewer would run them: typecheck, lint, format, the scope audit,
the quality audit, the migration check, then every test suite, then smoke tests
that drive real processes over real HTTP.

Optional stages are **skipped with a named reason** rather than silently
omitted, so a green run cannot hide a missing dependency. If the console smoke
test says the stack is not running, start `npm run dev` and re-run.

Individual pieces:

```bash
npm test                 # unit, integration, contract, policy, security
npm run test:e2e
npm run test:chaos       # dependency loss must fail closed
npm run test:python
npm run scope:audit      # the out-of-scope features must not exist
npm run quality:audit    # no mock data, no hardcoded metrics, no auth bypass
```

## Ports, and what to do when they collide

`4000` and `5173` are commonly taken on a developer machine, so expect to change
at least one of these. Change it in `.env` — everything reads from there,
including the console's Vite config, so one edit is enough.

The console binds with `strictPort`, so a collision is a startup error rather
than a silent move to another port.

| Variable | Default |
| --- | --- |
| `CONTROL_PLANE_PORT` | `4000` |
| `GATEWAY_PORT` | `4010` |
| `VEEFORE_CONSOLE_PORT` | `5173` |
| `VEEFORE_CONSOLE_PREVIEW_PORT` | `4173` |
| `VEEFORE_DOCS_PORT` | `4800` |
| `TOOL_SERVER_PORT` | `4500` (the example downstream service) |

## Next

:::cards

:::card Quickstart | /quickstart
Protect an agent and get a real decision.
:::

:::card Run the stack | /operate/run-the-stack
What each process does and how to operate it.
:::

:::card Configuration | /reference/configuration
Every environment variable, generated from `.env.example`.
:::

:::card Troubleshooting | /operate/troubleshooting
The failures people actually hit, and what causes them.
:::

:::
