Self-host Veefore
Run the control plane, the gateway and the console on your own infrastructure, against your own PostgreSQL.
Using hosted Veefore?
You do not need this page. Install the CLI and run veefore in your agent
project; see the 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.
#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
Install dependencies
bashgit clone https://github.com/veefore/veefore.git
cd veefore
npm install
Start a database
Any reachable PostgreSQL works. If you want one in a container:
bashdocker 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.
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.
Configure
bashcp .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.
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.
Migrate and seed
bashnpm 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.
Start everything
bashnpm 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:
bashnpm run dev:control-plane
npm run dev:gateway
npm run dev:console
Confirm it is healthy
bashcurl -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:
bashnpm 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.
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.
#Run the documentation locally
These docs are a workspace in the same repository.
bashnpm 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
bashnpm 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:
bashnpm 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) |