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.
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.
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
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='*'
| 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. |
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 weak
AXIOM_FIREWALL_SESSION_SECRET is a runtime warning the team can
miss in a busy log stream. With it, the app refuses to boot — the
failure surfaces in docker compose up immediately and you fix it
before traffic arrives.
Generate a real AXIOM_FIREWALL_SESSION_SECRET and 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.
AXIOM_MASTER_KEY stable 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 rotating AXIOM_MASTER_KEY after 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
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.
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.
| Path | Purpose | Code |
|---|---|---|
/healthz |
Liveness — process is up | 200 |
/readyz |
Readiness — DB writable | 200 / 503 |
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.
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.