Deployment Guide
May 16, 2026 · View on GitHub
This guide covers running Statewave in different environments.
Statewave is vendor-neutral: the API is a plain Python /
ASGI process that reads its config from STATEWAVE_* environment
variables and talks to a Postgres database. Anywhere you can run a
Python or Docker process — your laptop, a VPS, a container platform,
Kubernetes, a hyperscaler — Statewave runs the same way. The
Docker single-container recipe (Section 3) is the canonical
production deploy; the Fly.io and Railway sections that follow
are concrete examples of that recipe on two specific platforms, not a
preferred path.
Hardware: Statewave's API process is CPU-only — no GPU is required, and none of the deployment recipes below assume one. GPUs only enter the picture if you choose to self-host an LLM compiler or embedding model. See Hardware & Scaling.
Sizing: for tier-by-tier hardware profiles (local / small production / growing / enterprise) and topology patterns, see the Deployment Sizing Guide.
Already running, things are slow? See the Capacity Planning & Tuning Checklist — diagnostic flow, tuning order, and when to move up a tier.
1. Local Development (Docker Compose)
The fastest way to run Statewave locally.
Prerequisites
- Docker & Docker Compose
- Git
Steps
git clone https://github.com/smaramwbc/statewave.git
cd statewave
# Copy and optionally edit environment config
cp .env.example .env
# Start Postgres + API
docker compose up -d
# Run migrations
docker compose exec api alembic upgrade head
# Verify
curl http://localhost:8100/healthz```
The API is available at `http://localhost:8100`. Postgres data persists in the `pgdata` volume.
### Useful commands
```bash
docker compose logs -f api # tail API logs
docker compose down # stop services
docker compose down -v # stop + delete data
2. Local Development (bare metal)
Run without Docker — useful for debugging or IDE integration.
Prerequisites
- Python 3.11+
- PostgreSQL 16 with pgvector extension
- Git
Steps
git clone https://github.com/smaramwbc/statewave.git
cd statewave
# Create and activate venv
# Windows users (powershell): `python -m venv .venv ; .\.venv\Scripts\Activate.ps1`
# Windows users (cmd): `python -m venv .venv && .venv\Scripts\activate.bat`
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,llm]"
# Start Postgres (or use docker compose up db -d for just the database).
# As a Postgres superuser, create the role and database, then install the
# pgvector extension into that database. CREATE EXTENSION requires a
# superuser; the statewave app role itself does not need elevated rights:
# CREATE ROLE statewave WITH LOGIN PASSWORD 'statewave';
# CREATE DATABASE statewave OWNER statewave;
# \c statewave
# CREATE EXTENSION IF NOT EXISTS vector;
# Set connection string
# Windows (powershell): `$env:STATEWAVE_DATABASE_URL = "postgresql+asyncpg://statewave:statewave@localhost:5432/statewave"`
# Windows (cmd): `set STATEWAVE_DATABASE_URL=postgresql+asyncpg://statewave:statewave@localhost:5432/statewave`
export STATEWAVE_DATABASE_URL=postgresql+asyncpg://statewave:statewave@localhost:5432/statewave
# Run migrations
alembic upgrade head
# Start server
uvicorn server.app:app --reload --port 8100
3. Single-Container Deployment (canonical production recipe)
Deploy Statewave as a single container pointing to an external Postgres with the pgvector extension. This is the recipe every platform-specific section below builds on — the only thing that changes from one platform to the next is how you set the environment variables and who runs the container.
docker build -t statewave .
docker run -d \
--name statewave \
-p 8100:8100 \
-e STATEWAVE_DATABASE_URL=postgresql+asyncpg://user:pass@your-db-host:5432/statewave \
-e STATEWAVE_DEBUG=false \
-e STATEWAVE_API_KEY=your-secret-key \
statewave
Run migrations against the remote database before first start:
docker run --rm \
-e STATEWAVE_DATABASE_URL=postgresql+asyncpg://user:pass@your-db-host:5432/statewave \
statewave \
alembic upgrade head
4. Fly.io (platform example)
One concrete platform recipe for the single-container deploy in Section 3. Choosing Fly is a vendor decision — Statewave itself has no Fly-specific code path. Skip this section if you're deploying elsewhere; nothing in the rest of the docs depends on Fly.
Prerequisites
flyctlCLI installed and authenticated- A Fly Postgres cluster (or external Postgres)
Steps
cd statewave
# Create app
fly launch --no-deploy
# Set secrets
fly secrets set \
STATEWAVE_DATABASE_URL=postgresql+asyncpg://user:pass@your-fly-db.internal:5432/statewave \
STATEWAVE_API_KEY=your-secret-key
# Deploy
fly deploy
# Run migrations (one-off machine)
fly machine run . --command "alembic upgrade head" --rm
A minimal fly.toml:
[http_service]
internal_port = 8100
force_https = true
[env]
STATEWAVE_HOST = "0.0.0.0"
STATEWAVE_PORT = "8100"
STATEWAVE_DEBUG = "false"
5. Railway (platform example)
Another concrete recipe for the single-container deploy in Section 3. Railway provisions Postgres for you and wires it through. As with Fly, this section is a worked example, not a recommendation.
Steps
- Connect your
statewaverepo to Railway - Add a Postgres service (Railway provisions pgvector-capable Postgres)
- Set environment variables:
STATEWAVE_DATABASE_URL→ Railway providesDATABASE_URL; convert to asyncpg format: replacepostgresql://withpostgresql+asyncpg://STATEWAVE_API_KEY→ your secretSTATEWAVE_DEBUG→false
- Set start command:
alembic upgrade head && uvicorn server.app:app --host 0.0.0.0 --port $PORT - Deploy
6. Kubernetes (in-tree Helm chart)
Another concrete recipe for the single-container deploy in Section 3 — this one runs the API as a
Deploymenton Kubernetes. The chart is API-only; you bring a pgvector-capable Postgres reachable from the cluster.
The full walkthrough — secret-management patterns, Postgres options, Ingress timeout cheatsheet, HPA and connection-budget guidance, k8s-specific troubleshooting — lives in the dedicated Kubernetes Deployment page. The five-second sketch:
helm install statewave \
https://github.com/smaramwbc/statewave/releases/download/helm-0.1.0/statewave-0.1.0.tgz \
--namespace statewave --create-namespace \
--set database.url='postgresql+asyncpg://USER:PASS@db.example.com:5432/statewave' \
--set llm.apiKey='sk-…' \
--set auth.apiKey='replace-me'
Schema migrations run as a Helm pre-install + pre-upgrade Job (alembic upgrade head); the Deployment never serves traffic against an out-of-date schema. For multi-replica deploys, walk the connection-budget runbook in horizontal-scaling.md before raising replicaCount.
Environment Variables Reference
See .env.example for all available STATEWAVE_* configuration options.
LLM and embedding provider configuration
Two settings decide whether you get real semantic memory: the compiler and the embedding provider. Both route through LiteLLM, so any LiteLLM-supported provider works by changing the model identifier. Without either, Statewave runs in demo mode (regex extraction + hash-based embeddings, no semantic search).
Hosted provider (OpenAI, Anthropic, Azure, Bedrock, …)
STATEWAVE_COMPILER_TYPE=llm
STATEWAVE_EMBEDDING_PROVIDER=litellm
STATEWAVE_LITELLM_API_KEY=sk-...
STATEWAVE_LITELLM_MODEL=gpt-4o-mini
STATEWAVE_LITELLM_EMBEDDING_MODEL=text-embedding-3-small
Local, no API key (Ollama / self-hosted)
A model identifier starting with ollama/ tells Statewave the provider is local: it leaves STATEWAVE_LITELLM_API_KEY unset by design, suppresses the missing-key startup warning, and makes /readyz probe the local server instead of reporting a missing key (issue #122).
STATEWAVE_COMPILER_TYPE=llm
STATEWAVE_LITELLM_MODEL=ollama/llama3
STATEWAVE_LITELLM_API_BASE=http://host.docker.internal:11434 # API container → host Ollama
host.docker.internal resolves on Docker Desktop; on Linux add extra_hosts: ["host.docker.internal:host-gateway"] to the api service or use the host's LAN IP.
Embedding dimensions — the local-embeddings constraint
The pgvector column is fixed at 1536 dimensions (migration 0013; changing it means ALTERing the column type and rebuilding the HNSW index). STATEWAVE_LITELLM_EMBEDDING_MODEL must produce vectors of that size — Statewave does not resize the provider's output.
| Embedding source | Native dims | Works against the default schema? |
|---|---|---|
OpenAI text-embedding-3-small / -large | 1536 (configurable) | Yes |
Ollama nomic-embed-text | 768 | No — insert fails without a migration |
Ollama mxbai-embed-large | 1024 | No — insert fails without a migration |
A fully key-free deployment therefore has three honest options:
- Ollama compiler +
STATEWAVE_EMBEDDING_PROVIDER=stub— LLM extraction is local; retrieval is keyword/text only (no semantic vectors). Simplest, zero egress. Good for first-touch local exploration. - Ollama compiler + hosted embedding provider — compilation stays local; only short embedding inputs leave the network. Keeps semantic search. Best when semantic recall matters but egress should be minimal.
- Ollama compiler + local embedding model + a schema migration to the model's native dimension. Fully local semantic search, at the cost of maintaining a custom migration. Only when zero egress is a hard requirement.
See Compiler Modes for the compiler trade-offs and Privacy & Data Flow for what each configuration sends where.
Production Checklist
-
STATEWAVE_DEBUG=false -
STATEWAVE_API_KEYset to a strong secret -
STATEWAVE_CORS_ORIGINSrestricted to your domain(s) -
STATEWAVE_RATE_LIMIT_RPMset (e.g., 120) -
STATEWAVE_KIND_TTL_DAYSconfigured if you want stale memories to stop influencing/v1/contextautomatically — see Memory TTL - Database backups configured
- Migrations run before first request
- Health endpoint monitored (
GET /healthz)
Admin Console (optional)
For operator visibility into your running Statewave instance, you can deploy the admin console.
The admin console provides:
- System readiness and database health status
- Compile job monitoring
- Webhook delivery status
- Usage metering (episodes, memories, compiles)
- Subject health distribution
How to run it
Three paths, all equivalent — pick whichever fits your deployment:
1. Bundled with the server via the default compose stack (recommended for self-hosters):
The statewave/docker-compose.yml stack includes an admin service that pulls statewavedev/statewave-admin from Docker Hub. docker compose up -d brings it up alongside the API.
2. Standalone Docker container (any orchestrator — Kubernetes, Nomad, ECS, App Runner, Cloud Run, Render, …):
docker run -d --name statewave-admin -p 8080:8080 \
-e STATEWAVE_API_URL=https://your-statewave-instance \
-e STATEWAVE_API_KEY=$STATEWAVE_API_KEY \
-e ADMIN_PASSWORD=$ADMIN_PASSWORD \
-e ADMIN_SESSION_SECRET=$ADMIN_SESSION_SECRET \
statewavedev/statewave-admin:latest
3. From source (Node ≥18, useful when contributing to the admin codebase):
See statewave-admin/README.md.
Production-mode auth
In production you must set both ADMIN_PASSWORD and ADMIN_SESSION_SECRET, and must not set ADMIN_AUTH_DISABLED=true. The compose default ships ADMIN_AUTH_DISABLED=true for local dev — production deployments override this by leaving it empty in your .env and supplying real values for the other two:
ADMIN_AUTH_DISABLED= # required: leave empty
ADMIN_PASSWORD=$(openssl rand -base64 32)
ADMIN_SESSION_SECRET=$(openssl rand -hex 32)
Important: even with the password gate enabled, the admin console is an internal tool. Deploy it behind an access gateway (Cloudflare Access, OAuth2 Proxy, identity-aware proxy, …) — do not expose publicly. See statewave-admin/SECURITY.md for the full security posture.