Veefore docs

Deployment

Containers, Kubernetes, Helm and Terraform — plus the network topology that makes the plane split worth having.

The repository ships deployment definitions under infrastructure/, validated by npm run infra:validate as a stage of npm run verify.

infrastructure/docker/Dockerfile Multi-stage build for all three Node processes
infrastructure/docker/docker-compose.yml Single-node stack with PostgreSQL
infrastructure/kubernetes/base.yaml Namespace, config, secrets, service accounts
infrastructure/kubernetes/control-plane.yaml Deployment, service, probes, HPA
infrastructure/kubernetes/gateway.yaml Deployment, service, probes, HPA
infrastructure/helm/veefore/ Chart and values
infrastructure/terraform/ Database, networking, secret references

#Build

bash
npm run build                 # backend and console
npm run build:backend
npm run build:console
bash
docker build -f infrastructure/docker/Dockerfile -t veefore:1.0.0 .

One image, three entry points. The processes share almost all their code, and a single image means one thing to scan, sign and promote.

bash
node dist/control-plane/server.js
node dist/gateway/server.js
node dist/control-plane/worker.js

#Network topology

This is the part that makes the plane split worth having.

text
                    Internet / your VPC
                            │
          ┌─────────────────┴─────────────────┐
          │                                   │
    ingress: console              ingress: gateway
    + control plane                /gateway/v1/* only
    /v1/* only                            │
          │                               │
   people, CLI, CI                     agents
          │                               │
          └───────────────┬───────────────┘
                          │
                   PostgreSQL (private)

Separate ingresses. Do not merge them.

The gateway is the only process agents reach, and it must expose only /gateway/v1/* and /.well-known/veefore-platform-key.

Exposing /v1/* on the ingress agents can reach undoes the design. The control plane is where policies are activated and credentials are registered; it has no business being reachable from wherever your agents run.

Correspondingly, do not expose /gateway/v1/* on the human-facing ingress. There is no reason for a browser to reach it.

The database is private to both planes. Nothing outside the cluster or VPC reaches it.

#Kubernetes

bash
kubectl apply -f infrastructure/kubernetes/base.yaml
kubectl apply -f infrastructure/kubernetes/control-plane.yaml
kubectl apply -f infrastructure/kubernetes/gateway.yaml

The probes are the important part of those manifests:

yaml
livenessProbe:
  httpGet: { path: /health/live, port: http }
  initialDelaySeconds: 10
  periodSeconds: 10
readinessProbe:
  httpGet: { path: /health/ready, port: http }
  initialDelaySeconds: 5
  periodSeconds: 5

/health/ready genuinely checks the database, so a pod with a broken connection leaves rotation instead of serving errors.

Both planes are stateless — session state and the job queue are in the database — so horizontal scaling needs no coordination. Scale them independently: the gateway sees high-volume, latency-sensitive agent traffic; the control plane sees low-volume, bursty human and CI traffic. 3:1 is a reasonable starting ratio.

Run at least one worker replica. Job claiming is tenant-safe and guarded against overlapping poll ticks, so several can run concurrently. With none, webhooks never deliver and usage never rolls up, and nothing visibly breaks.

#Secrets

bash
kubectl create secret generic veefore-keys \
  --from-literal=VEEFORE_DATA_ENCRYPTION_KEY=… \
  --from-literal=VEEFORE_SIGNING_PRIVATE_KEY=… \
  --from-literal=VEEFORE_SIGNING_KEY_ID=… \
  --from-literal=VEEFORE_SESSION_SECRET=… \
  --from-literal=VEEFORE_AUDIT_CHAIN_SECRET=…

Better: an external secrets operator pulling from your secret manager, so rotation does not mean editing a manifest.

Give each process only the secrets it needs

The gateway does not issue human sessions, so it does not need VEEFORE_SESSION_SECRET. The worker does not sign passports, so it does not need VEEFORE_SIGNING_PRIVATE_KEY.

A secret a process does not need is a secret that process should not have — it is one more place a compromise can read it from. The table in Run the stack lists what each one requires.

#Rollout order

  1. Migrations, as a job that completes before anything restarts. Forward-only and safe against the previous version.
  2. Control plane, rolling.
  3. Gateway, rolling.
  4. Worker. Any time; in-flight jobs are reclaimed.
  5. Console static assets.

Gateway after the control plane, so the control plane is already on the new schema when the gateway starts depending on it.

Enforcement mode is data, not configuration

Deploying does not change any environment's mode. It persists in the database and changes only through POST /v1/environments/{id}/gateway-mode or veefore deploy — with a reason, step-up authentication, and a CRITICAL audit event if it reduces enforcement.

This is deliberate: a mode that could be changed by a deploy could be changed by a misconfigured pipeline.

#TLS

Terminate at your ingress. Both planes speak HTTP behind it.

veefore doctor reports a control plane or gateway reachable over plaintext HTTP on a non-loopback host as a finding. Egress has the same rule enforced rather than reported: a tool endpoint must be HTTPS unless its host is loopback.

#Terraform

infrastructure/terraform/ provisions the database, the networking and the secret references. It does not provision the application — that is Helm or raw manifests, so you can move the application forward without touching infrastructure state.

bash
cd infrastructure/terraform
terraform init
terraform plan

#Validate before you ship

bash
npm run infra:validate

Checks that the container, Kubernetes, Helm and Terraform definitions are well formed. Part of npm run verify.

#Scaling signals

Signal Action
Gateway p99 decision latency rising More gateway replicas
Database connections near max_connections PgBouncer, or lower DATABASE_POOL_MAX
Job queue depth growing More worker replicas
Rate limits firing unexpectedly across instances Set REDIS_URL so limits are shared

That last one is a real trap: without Redis, each instance keeps its own rate limit and nonce state, so your effective limit is multiplied by your instance count and nonce replay protection is weaker than you think.

Identity, authority, policy and enforcement for autonomous AI agents.