Skip to content

Vectasec · Get started

Deployment

How to run the Vectasec engine, worker and console against Supabase, where the Collector fits, and the status of the self-hosted preview.

On this page

A Vectasec deployment has three processes: the engine API, one or more queue workers and the console. All three share one Postgres control plane on Supabase. This page covers how to run them, what the database needs, and what to set before anything is reachable from outside your machine.

Topologies#

  • Hosted control plane (default). A Supabase project provides Postgres, Auth, Vault, pgmq, pg_cron and Realtime, and you run the engine API, workers and console beside it. Workers scan cloud-mode connections directly, and the engine runs connection tests the same way, so both must be able to reach each cloud-mode target.
  • Collector. A process inside your network that runs the same connectors and detectors, connects outbound only and keeps credentials local. It pushes findings, their observations and resource metadata. Credentials and raw resource configuration stay in your network. The console and API cannot mint enrollment tokens yet. See Run a collector in your network.
  • Self-hosted stack. The self-hosted preview bundle (Docker Compose and a Helm chart). This is a preview, described at the end of this page.

Run the services#

Run the Python processes from the engine directory of the release. Each one loads the nearest .env at or above its working directory. Variables already set in the environment take precedence.

Engine API#

Start the engine API with its bundled entry point from the engine directory of the release (it reads .env, ENGINE_HOST and ENGINE_PORT); don't start it with bare uvicorn, which skips the .env load. The engine binds 127.0.0.1:8000 by default (ENGINE_HOST, ENGINE_PORT). Set ENGINE_HOST=0.0.0.0 only inside a container or a private network, because the engine holds a database connection that bypasses row-level security. Set SUPABASE_URL so the engine can verify user JWTs.

The console calls the engine from its server side, never from the browser. Only the console and, if you run them, collectors need to reach it.

Worker#

bash
uv run vectasec worker

The worker drains the scan queue (a pgmq queue), claiming up to 5 messages per read with a 600-second visibility timeout. You can run several workers, because a claimed message stays hidden from the others until it is archived or the timeout passes. If a worker dies mid-scan, the message reappears and the scan reruns. Findings dedupe on a fingerprint, so a rerun is safe.

A failed scan is retried until its message has been read 5 times, then archived as poison and sent to your scan-failed alert rules. Rejected credentials and configuration errors are archived after the first attempt, because no retry can fix them. Each loop also marks scans that have been running for more than an hour as error.

  • --once drains what is queued, then exits.
  • --reap-only marks stuck scans, then exits without touching the queue.

Console#

From the console directory of the release:

bash
pnpm install
pnpm build
pnpm start

The console is a Next.js 16 app. It reads NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, VECTASEC_ENGINE_URL (default http://127.0.0.1:8000) and VECTASEC_ENGINE_TOKEN, plus RESEND_API_KEY and EMAIL_FROM if it sends mail. Put them in the console's .env.local, because Next.js reads env files from the app directory, not the repository root. The NEXT_PUBLIC_* values are inlined into the browser bundle at build time, so rebuild when they change.

Scheduler#

Scheduled scans need no extra process. A scheduled pg_cron job runs every minute. It queues active connections whose scan_interval_minutes has elapsed and that have no scan already queued or running. You set the interval in the console or with PATCH /connections/{id}: 5 to 10080 minutes, or null to unschedule. A cloud-mode connection gets a scan record and a queue message for a worker. A collector-mode connection gets only the scan record, which its collector claims.

Database#

Apply every database migration shipped with the release, in order. There are 16 today, written to be idempotent, so you can reapply the full set.

The migrations create pgcrypto, pgmq and pg_cron. supabase_vault must already be installed, as it is on Supabase, because the engine stores connection credentials in Vault. The scheduler migration registers its job only when the cron schema exists. Without it, no scheduled scans run.

  • The engine and workers connect with DATABASE_URL as a role that bypasses RLS, and every engine query filters by tenant itself. Treat this URL as the most sensitive value in the deployment.
  • The console reads as authenticated, which has SELECT-only policies scoped to the user's tenants. Tenant tables use FORCE row-level security.

Health checks#

bash
curl -s http://127.0.0.1:8000/health

/health always returns HTTP 200 with {status, db, detectors, connectors}. db holds the real database state, and status reads degraded when it is false. Use the endpoint for liveness, and alert on db: false, not on the status code.

uv run vectasec doctor also checks the extensions and the scan queue: messages waiting, scans queued and scans running. A growing queue with nothing running means no worker is draining it. doctor exits non-zero when it finds a problem.

Production checklist#

Set these before anyone but you can reach the engine or the console.

SettingWhereWhy
VECTASEC_ENGINE_TOKENEngine and console, same random valueThe console authenticates to the engine with it. Without it, the engine rejects the console's calls with 401 and console actions fail.
VECTASEC_ENGINE_TOKEN_TENANTEngine, single-tenant installsPins the service token to one tenant. A request that names another tenant is refused.
VECTASEC_COLLECTOR_TOKEN_SECRETEngineSigns collector session tokens. Use an independent random value. When it is unset, the key is derived from DATABASE_URL.
VECTASEC_DEV_TRUST_HEADERSEngineLeave unset. It is a single-operator development switch that trusts self-asserted tenant and actor headers.
VECTASEC_ALLOW_SERVICE_SUPERADMINEngineLeave unset. When enabled, service-token calls that name a platform super admin act as admin in any tenant.
ENGINE_CORS_ORIGINSEngineSet to your console origin. The default is http://localhost:3000.
SUPABASE_SERVICE_ROLE_KEY, DATABASE_URLEngine side onlyBoth bypass RLS. The console must never receive either one.

Then:

  • Keep the engine off the public internet, because it holds a privileged database connection. If collectors connect from other networks, expose only the /collector/* routes to them, over TLS.
  • Run vectasec doctor --verify-ledger on a schedule. It recomputes the evidence hash chain, prints the first 20 breaks with a count of any others, and exits non-zero if it finds one. It reads every hashed observation, so run it off-peak on a large estate.

Self-hosted stack (preview)#

The self-hosted preview bundle contains:

  • A Docker Compose file with Supabase Postgres, GoTrue, PostgREST, Realtime, the engine, the console and Caddy. Caddy terminates TLS, serves everything from one origin and routes /api/* to the engine. A one-shot migrate service applies the migrations on every up.
  • Dockerfiles for the engine and the console.
  • A Helm chart for the engine, the console and an optional worker. It does not deploy Postgres or the Supabase services, and it does not apply migrations.

The compose file predates the engine's verified-token auth. Before you try it:

  • Apply the production checklist above.
  • Add VECTASEC_ENGINE_TOKEN to the engine and web services, and set VECTASEC_ENGINE_URL=http://engine:8000 on web. The chart sets neither, so on Kubernetes add both to the engine and web Deployments, with VECTASEC_ENGINE_URL pointing at the engine Service.
  • Set VECTASEC_COLLECTOR_TOKEN_SECRET on the engine.
  • Narrow VECTASEC_ENGINE_ALLOWED_CIDRS, the source ranges Caddy allows on /api/*, to your operator subnet. On Kubernetes, add a source-range restriction to the /api ingress path, because the chart sets none. The chart also does not strip the /api prefix, and the engine serves its routes at the root, so add your ingress controller's rewrite.
  • Add a queue worker. The compose worker profile and the chart's worker run the collector image, not vectasec worker. The engine image includes the vectasec CLI, so a service built from it, with the engine's environment, can run vectasec worker.

Also keep in mind:

  • Engine user-JWT verification accepts only ES256 or RS256 keys from the Auth JWKS endpoint. The bundled GoTrue signs HS256, so rely on the engine-token path, which the console already uses.
  • NEXT_PUBLIC_* values are baked in at build time, so build the console image per hostname.
  • For an air-gapped runtime, leave ANTHROPIC_API_KEY, OPENAI_API_KEY and GEMINI_API_KEY blank so triage writes its deterministic summary, and set VECTASEC_OSV_DISABLED=1 and VECTASEC_NVD_DISABLED=1 wherever scans run: the engine and every worker. The compose file does not pass those two switches through to the engine, so add them to its environment. Without them, a scan that finds components to check records a not-assessable coverage-gap finding for each feed it cannot reach. Building the images needs network access once.

Next steps#