Self-hosting
The Orivael Intent Firewall runs as a single FastAPI app. You can host it anywhere you can run Python 3.11+ or a Docker container.
This guide is the short version. For the prod-grade AWS deploy, see
deploy/firewall/README.md.
Minimum runtime
- Python 3.11+ (3.10 works but isn't tested)
- 256 MB RAM, 1 vCPU per replica
- A persistent volume for tenant SQLite files
- A way to terminate TLS (Caddy, nginx, Traefik, ALB, Cloudflare Tunnel — your choice)
Run with Docker
docker build -t orivael/axiom-firewall:local \
-f deploy/firewall/Dockerfile .
docker run -d --name firewall \
-p 8004:8004 \
-e AXIOM_MASTER_KEY="$(openssl rand -hex 32)" \
-e AXIOM_FIREWALL_SESSION_SECRET="$(openssl rand -hex 32)" \
-v /var/lib/axiom-firewall:/data/tenants \
--restart unless-stopped \
orivael/axiom-firewall:local
Hit http://localhost:8004/healthz to verify.
Run with docker-compose + Caddy (auto TLS)
See deploy/firewall/docker-compose.yml and deploy/firewall/Caddyfile.
cd deploy/firewall
cp .env.example .env
# fill in AXIOM_ENV=production, AXIOM_MASTER_KEY,
# AXIOM_FIREWALL_SESSION_SECRET, FIREWALL_HOST. See the Production
# checklist below for what each one does and the rotation gotchas.
docker compose --profile tls up -d
Run from source
git clone https://github.com/Orivael-Dev/axiom
cd axiom
pip install fastapi uvicorn jinja2 python-multipart itsdangerous
export AXIOM_MASTER_KEY=$(openssl rand -hex 32)
export AXIOM_FIREWALL_SESSION_SECRET=$(openssl rand -hex 32)
uvicorn axiom_firewall.dashboard:app \
--host 0.0.0.0 --port 8004 \
--workers 2 --proxy-headers --forwarded-allow-ips='*'
Required environment
| Variable | Required | Purpose |
|---|---|---|
AXIOM_ENV |
prod | Set to production in any prod deploy. Turns the session-secret check into a hard error (boot fails instead of warning) and forces the Secure flag on session cookies. Defaults to development. |
AXIOM_MASTER_KEY |
yes | HMAC root for signing verdicts and the pepper for the API-key hash table. 64 hex chars. Must stay stable across restarts — see Production checklist below. |
AXIOM_FIREWALL_SESSION_SECRET |
yes | Cookie signing key. 32+ chars; with AXIOM_ENV=production the app refuses to boot if this is the dev default or shorter. |
AXIOM_FIREWALL_TENANT_DIR |
no | Where to keep tenant SQLite files. Default tenants. Mount this to a persistent volume. |
AXIOM_FIREWALL_PUBLIC_URL |
no | The URL the dashboard is served at. Used for Stripe redirects. |
AXIOM_FIREWALL_CORS_ORIGINS |
no | Comma-separated origins permitted to call /v1/guard/check from a browser. Empty = server-side only. |
AXIOM_GUARD_API_TOKEN |
compose | Bearer token callers must present on the guard-api's OpenAI-compatible /v1/chat/completions (drop-in path). Required by the compose guard-api service — on a public host every proxied call spends your provider key. Optional (off) when running the guard API alone for local development. |
ANTHROPIC_API_KEY / NIM_API_KEY |
no | The model backend the guard-api proxies to (Anthropic, or any OpenAI-compatible endpoint via NIM_BASE_URL + NIM_MODEL). Without one, /guard/* verdicts still work — only the proxy paths 503. |
Production checklist
Three gotchas to wire into your prod compose file / k8s manifest before the first request lands:
-
Set
AXIOM_ENV=production. Without it, a missing or weakAXIOM_FIREWALL_SESSION_SECRETis a runtime warning the team can miss in a busy log stream. With it, the app refuses to boot — the failure surfaces indocker compose upimmediately and you fix it before traffic arrives. -
Generate a real
AXIOM_FIREWALL_SESSION_SECRETand put it in your secret store. 32 chars minimum (the boot check enforces this in prod):
python3 -c 'import secrets; print(secrets.token_hex(32))'
If this leaks, anyone can forge session cookies for any tenant. Rotate the value to force a global logout.
- Keep
AXIOM_MASTER_KEYstable across restarts. It's used in two places now: - HMAC signing of every verdict (changes invalidate prior receipts). - The peppered hash of API keys stored in the tenant SQLite files. The legacy plaintext-to-hash migration runs once at write time, not on every read — so rotatingAXIOM_MASTER_KEYafter keys are in the DB makes every existing API key fail authentication permanently.
Treat it like a database encryption key: generate once, store in AWS Secrets Manager / GCP Secret Manager / a sealed-secrets CRD, never rotate without a migration plan. If you must rotate, the safe sequence is: pause traffic → re-hash every row with the new pepper → swap the env var → resume.
A minimal prod .env snippet that satisfies all three:
AXIOM_ENV=production
AXIOM_MASTER_KEY=<64 hex chars, generated once and stored in your secret manager>
AXIOM_FIREWALL_SESSION_SECRET=<64 hex chars, rotatable>
AXIOM_FIREWALL_TENANT_DIR=/data/tenants
Optional: enable Stripe billing
Run scripts/stripe_setup.py with your test- or live-mode secret
key. It prints the env vars to set:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # from the webhook endpoint you create
STRIPE_PRICE_INDIE=price_...
STRIPE_PRICE_TEAM=price_...
STRIPE_METER_INDIE=axiom_firewall_indie
STRIPE_METER_TEAM=axiom_firewall_team
If you leave Stripe unset, the Firewall runs in free-tier-only mode: all paid-tier routes return 503, free tier signup + verdict path work.
Behind a reverse proxy
The Firewall trusts X-Forwarded-* headers when started with
--proxy-headers --forwarded-allow-ips='*'. Pass through:
| Header | What it's used for |
|---|---|
X-Forwarded-For |
Client IP (for per-IP signup rate limit). |
X-Forwarded-Proto |
Required so redirects use https://. |
X-Forwarded-Host |
Optional; only used for log output. |
Restrict --forwarded-allow-ips to your proxy's CIDR when the upstream
is on a known network.
Health endpoints
| Path | Purpose | Code |
|---|---|---|
/healthz |
Liveness — process is up | 200 |
/readyz |
Readiness — DB writable | 200 / 503 |
Backups
Tenant SQLite files are self-contained. Back up $AXIOM_FIREWALL_TENANT_DIR
on the cadence your data-retention policy requires:
tar czf /backups/axiom-firewall-$(date +%Y%m%d).tar.gz \
-C $AXIOM_FIREWALL_TENANT_DIR .
Restoring is a tar xzf into a new mount on a fresh deploy.
Upgrades
The dashboard performs ALTER TABLE ... ADD COLUMN migrations on
startup automatically. Forward-only — no destructive migrations are
planned through Phase 3. Read the release notes before upgrading
across a major release.