Deployment
Local development runs on SQLite with zero infrastructure. Production runs on
PostgreSQL (asyncpg), with Prometheus metrics at /metrics, optional
OpenTelemetry tracing, rotating file logs, and a liveness/readiness probe at
/health.
This page walks through setting up the service’s config.yaml in the order
you’d actually configure it, plus the SQLite-vs-Postgres decision and the
migration mechanism behind it — the operational detail that applies no
matter which platform you’re running on. For how to actually get VectorStep
running on a given platform, see Installation
(Docker, Kubernetes, Linux, macOS, Windows). For the exhaustive
field-by-field reference of every key in config.yaml, see Configuration
reference. For what’s trusted and what isn’t, and
the ordered checklist for putting this in front of real users, see Threat
model and Securing a
deployment.
Service configuration
Section titled “Service configuration”The installer writes ~/.vectorstep/config/vectorstep.yaml, bind-mounted
read-only into the container at /etc/vectorstep/config.yaml; edit the host
copy and work through it top to bottom. pipeline_config_dir,
step_library_dir, artifacts.dir, logging.dir, and the SQLite path all
resolve inside the container’s /data volume, not the working directory the
process happens to start in — a relative path like ./runs.db would try to
write under /app, which the non-root container user can’t do:
server: host: 0.0.0.0 port: 8000
pipeline_config_dir: /data/pipelinesstep_library_dir: /data/steps # reusable step definitions; omit to disable library
database: url: sqlite+aiosqlite:////data/db/runs.db # four slashes = absolute path # url: postgresql+asyncpg://vectorstep:vectorstep@postgres:5432/vectorstep # production — see Database below # auto_migrate: true # run pending migrations on boot; false hands control to a DBA
notifications: telegram: bot_token: ${TELEGRAM_BOT_TOKEN} chat_id: ${TELEGRAM_CHAT_ID}
executors: openclaw: url: ws://127.0.0.1:18789/rpc # OpenClaw Gateway WebSocket URL gateway: url: ws://gateway:18780/rpc # VectorStep Gateway WebSocket URL — the compose service name, not localhost token: ${VECTORSTEP_GATEWAY_TOKEN} # Bearer token; empty string for local dev rest_url: http://gateway:18780 # VectorStep Gateway REST base URL (used by Agents UI)
logging: level: INFO dir: /data/logs # omit to disable file logging (stdout only) # creates service.log and access.log (rotating, 10 MB × 5)
artifacts: dir: /data/artifacts # omit this block entirely to disable artifact storage retention_days: 7 # artifact directories older than this are removed daily at 02:00
dedup: enabled: true # omit this block (or set false) to disable dedup entirely window_seconds: 300 # overridable per-pipeline via trigger.dedup
concurrency: max_runs: 10 # maximum simultaneous pipeline executions (default: 10). # POST /webhook returns 429 when at capacity. # GET /health exposes active_runs / max_concurrent_runs.
auth: tokens: # named tokens, each carrying a role — see /docs/operations/security/ - name: platform-admin token: ${VS_TOKEN_PLATFORM_ADMIN} role: admin # full access, incl. pipeline/step writes and /reload - name: sre-oncall token: ${VS_TOKEN_SRE} role: operator # trigger/rerun/replay/feedback/approvals, no config writes - name: dashboards token: ${VS_TOKEN_VIEWER} role: viewer # read-only - name: payments-alerts token: ${VS_TOKEN_PAYMENTS} role: webhook # POST /webhook only team: payments # webhook-role only — resolves `team` attribution on every run # it authenticates. Alertmanager sends its token via # http_config.authorization.credentials — route different teams' # alerts to different receivers with different tokens. # allow_unauthenticated: true # refuses to start with no tokens configured unless this is set — # only appropriate on a trusted local machine, never in production.
security: allow_shell_checks: false # default — see Configuration reference before enabling template_sandbox: true # default
observability: otel: enabled: false # omit this block (or set false) to disable tracing entirely exporter: otlp # otlp | console — see /docs/operations/observability/ endpoint: http://localhost:4318/v1/traces service_name: vectorstep-service
calibration: # omit this block entirely for the defaults shown below n_min: 20 # marked outcomes required before a bucket is "validated" bin_width: 0.1 # must evenly divide 1.0 — see Calibration cache_ttl_seconds: 300 # how long the in-process bucket cache is reused before refetching${ENV_VAR} placeholders are resolved at startup from the container’s own
environment — every variable in ~/.vectorstep/.env reaches it. Unresolved
placeholders become "".
The order above roughly matches setup order in practice: get server and
database right first, wire up an executor so steps can actually run, add
auth.teams once you’re ready to accept real webhooks from more than one
team, and turn on observability/calibration once the basics are working.
For the meaning of every individual field, see
Configuration reference.
security is worth reading before you expose this beyond a trusted
network. Both keys default to the safe value — allow_shell_checks: false
means a pipeline author can’t get a shell on this host through a type: shell deterministic check, and template_sandbox: true blocks arbitrary
Python execution from a crafted Jinja2 template in pipeline config. Neither
default needs changing for a typical deployment; see Configuration
reference for what enabling either one
gives up.
Database
Section titled “Database”The ORM layer (SQLAlchemy async) is dialect-agnostic — switching backends is a
database.url change only, no code changes. Two supported backends:
| Backend | URL | When to use |
|---|---|---|
SQLite (aiosqlite) |
sqlite+aiosqlite:///./runs.db |
Local dev, zero infrastructure, single process |
PostgreSQL (asyncpg) |
postgresql+asyncpg://user:pass@host:5432/dbname |
Production — concurrent writers, real backup/replication story |
Setup (Postgres) — the easy path:
curl -sSL https://raw.githubusercontent.com/bantex01/VectorStep-Dist/main/install.sh | bash -s -- --postgres--postgres runs PostgreSQL in its own container, generates a random
password into ~/.vectorstep/.env, and points database.url at it —
nothing to install or configure by hand. It refuses on an existing SQLite
install, since switching backends doesn’t migrate data; it’s a fresh-install
option only.
Bring your own Postgres instead:
createdb vectorstep# config/vectorstep.yaml:database: url: postgresql+asyncpg://user:password@your-host:5432/vectorstepSchema migrations run automatically on startup via
Alembic (create_tables(), calling
alembic upgrade head programmatically) — same as SQLite, no manual step for
a normal boot.
Migration mechanism. The revision history and Base.metadata (the ORM
models) ship inside the image at /app; revisions are generated with
alembic revision --autogenerate and reviewed, never trusted blind. On boot,
create_tables() adopts whatever
state the database is already in:
- Already stamped at head → no-op.
- A brand-new, empty database →
alembic upgrade headfrom scratch.
Set database.auto_migrate: false (default true) to take migrations out of
the boot path entirely — for a DBA-controlled deployment. Startup then fails
fast, naming the pending revisions, if the schema is behind head, instead of
migrating it for you. Run migrations yourself against a running container:
cd ~/.vectorstepdocker compose exec vectorstep alembic upgrade headalembic.ini and the revision history ship inside the image at /app, so this
needs nothing on the host. On Kubernetes, the same command through kubectl:
kubectl exec deploy/vectorstep -- alembic upgrade headRun it as a one-shot Job before rolling a new image if you’d rather the
migration not depend on a pod that is already serving traffic.
Dedup race hardening: a partial unique index —
UNIQUE (pipeline_name, fingerprint) WHERE status = 'running' — closes a
TOCTOU race at the database layer, not just the application-level pre-check.
See Webhooks for the full dedup mechanism.
There is no TLS by default — every example on this site and every shipped
config uses plain http:// and ws://. Nothing in the product terminates
or verifies TLS until you configure it. Two ways to add it, and most
deployments want a mix of both: an in-process server.tls block for the
service-to-Gateway hop, and a terminating reverse proxy for the browser edge.
In-process TLS
Section titled “In-process TLS”server: host: 0.0.0.0 port: 8000 tls: cert: /etc/vectorstep/tls/server.crt # PEM; omit the whole block to serve plain HTTP key: /etc/vectorstep/tls/server.key key_password: ${VS_TLS_KEY_PASSWORD} # optional, if the key is encrypted client_ca: /etc/vectorstep/tls/ca.crt # optional; presence enables mTLSStartup fails fast, naming the path, if cert is set without key, or if
either file doesn’t exist — a mistyped certificate path serving plain HTTP by
accident would be worse than a crash. There’s no certificate generation or
ACME support built in; point cert/key at your own PKI’s output, or use
the reverse-proxy pattern below instead.
This same server.tls block configures VectorStep Gateway too — the
shape is identical between the two services. See Gateway
configuration for the Gateway side and the
wss:// client settings VectorStep needs to talk to it once it’s TLS-enabled.
uvicorn --reload (the local dev command) always serves plain HTTP — it
reads no config and has no TLS option. That’s fine for local development;
don’t run it as a deployment target.
Reverse proxy (recommended for the browser edge)
Section titled “Reverse proxy (recommended for the browser edge)”Most enterprises terminate browser-facing TLS at an ingress or load balancer
rather than in-process in the container — it’s usually where certificate
rotation and the rest of the org’s TLS tooling already lives. The
service-to-Gateway hop is different: it’s service-to-service, usually not
proxied, and it’s the hop carrying the Gateway’s bearer token and the UI
session cookie’s underlying credential, so that one wants real wss://
verification (above), not just a proxy in front of it.
Caddy (automatic certificates via ACME):
vectorstep.example.com { reverse_proxy 127.0.0.1:8000}nginx:
server { listen 443 ssl; server_name vectorstep.example.com;
ssl_certificate /etc/nginx/tls/vectorstep.crt; ssl_certificate_key /etc/nginx/tls/vectorstep.key;
location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}forwarded_allow_ips
Section titled “forwarded_allow_ips”Behind a terminating proxy, VectorStep itself only ever sees plain HTTP — it
has to trust the proxy’s X-Forwarded-Proto header to know the original
client connection was TLS. That trust isn’t automatic:
server: forwarded_allow_ips: "127.0.0.1" # default; or a proxy CIDRThis matters concretely: the UI session cookie only sets its Secure flag
when the request is recognised as TLS, and a request is only recognised as
TLS when it terminated in-process (above) or arrived from an IP in
forwarded_allow_ips claiming X-Forwarded-Proto: https. Default this to
your proxy’s address, not to "*" — a wildcard lets any client set that
header itself and convince VectorStep a plaintext connection was secure,
which silently drops the Secure cookie requirement. Set it to the specific
loopback address or CIDR your proxy actually connects from.
Resource sizing (Kubernetes)
Section titled “Resource sizing (Kubernetes)”The shipped deployment.yamls set starting resource values — tune them for
your workload, don’t take them as authoritative:
| Component | requests | limits |
|---|---|---|
vectorstep |
cpu: 100m, memory: 256Mi |
cpu: 1, memory: 1Gi |
vectorstep-gateway |
cpu: 200m, memory: 512Mi |
cpu: 2, memory: 2Gi |
The Gateway’s are higher because it spawns MCP server subprocesses inside its
own container, and its limits.max_concurrent_runs (see Gateway
configuration) directly multiplies memory use
— raise the memory limit if you raise that. VectorStep’s own
concurrency.max_runs has a similar relationship to its CPU/memory
footprint, though a CPU limit is comparatively safe there specifically
because its work is I/O-bound waiting on LLM calls, not compute-bound.
Where next
Section titled “Where next”- Installation — how to actually get VectorStep and the Gateway running: Docker (image tags, config-mounting convention, docker-compose evaluation path), Kubernetes (manifests, the single-replica constraint), Linux (Docker install and systemd), macOS, and Windows (WSL2).
- Configuration reference — every
config.yamlfield, exhaustively. - Threat model and Securing a deployment — what’s trusted, what a compromised credential yields, and the ordered checklist before this faces real traffic.