Deployment Guide¶
Production deployment reference for the AegisAgent gateway: Docker Compose, Kubernetes (Helm), and bare metal, plus an environment variable reference, a production checklist, and capacity planning grounded in measured benchmarks.
This page assumes you've already read docs/getting-started.md or docs/quickstart.md for the zero-setup demo. It's about running the gateway as a real, long-lived service.
Status: The known-agent gateway is production-hardened for a single-writer topology. Runtime data-plane packaging and multi-replica PostgreSQL operations remain Partial; check Implementation Status.
Overview¶
This guide takes an operator from a tested artifact to a running, observable, recoverable gateway. It covers the shipped deployment paths, their trust boundaries, configuration, capacity constraints, verification, rollback, backup, and disaster recovery.
Why This Exists¶
An authorization service is part of the action safety boundary. A deployment that exposes an unauthenticated listener, loses receipt evidence, runs multiple SQLite writers, or routes traffic before readiness can undermine otherwise-correct policy and SDK behavior. Deployment requirements therefore include security and evidence integrity, not only process availability.
Architecture¶
flowchart TB
AGENTS[Agent workloads] -->|HTTPS REST :8080 / gRPC :6334| EDGE[TLS ingress or controlled network]
ADMINS[Operators / approvers] --> EDGE
EDGE --> GW[AegisAgent gateway]
GW --> DB[(Durable relational storage)]
GW --> POL[Mounted Cedar policy]
KMS[Secret manager / KMS] --> GW
GW --> PROM[Prometheus]
GW -. OTLP / export .-> OBS[Collector / SIEM]
BACKUP[Encrypted backup target] <-->|backup / restore| DB
The listener is the outer trust boundary; authenticated tenant handling and policy form the decision boundary; the database and signer protect the evidence boundary. Keep loopback defaults for local development and add TLS/authentication before any public bind.
1. Docker Compose¶
The repo ships two compose files:
docker-compose.yml— the base gateway service. Starts with an empty database; you seed it yourself (bash scripts/seed-demo.sh), which is what CI's "Docker Compose E2E" job does.docker-compose.dev.yml—include:s the base file and layers on a one-shotseed-demoservice that runsscripts/seed-demo-dev.shautomatically once the gateway reports healthy. Use this for local development.
# Production-leaning: empty DB, seed yourself (or skip seeding entirely)
docker compose up --build -d
bash scripts/seed-demo.sh # optional — demo agents/tools/decisions
# Local dev: auto-seeded
docker compose -f docker-compose.dev.yml up --build -d
Both compose files run the gateway image built from src/Dockerfile — a multi-stage build that lands on gcr.io/distroless/cc-debian12:nonroot (no shell, no package manager, runs as uid/gid 65532). The base file's init-data-dir service is a one-shot busybox container that chmods the bind-mounted ./data directory before the gateway starts, since the host-side directory is created under your local user, not uid 65532.
For a real deployment, replace the bind mounts with named volumes and put a reverse proxy (or AEGIS_TLS_CERT/AEGIS_TLS_KEY, see §4) in front for TLS — network_mode: host in the shipped compose files is a local-dev convenience, not a production-hardening choice.
2. Kubernetes (Helm)¶
helm install aegis helm/aegis-gateway/ \
--set image.repository=<your-registry>/aegis-gateway \
--set image.tag=<your-tag>
The chart (helm/aegis-gateway/, #1206) ships Deployment, Service, ConfigMap (Cedar policy bundle), Secret, ServiceMonitor, NetworkPolicy, PodDisruptionBudget, HPA, ServiceAccount, and PVC templates. Read helm/aegis-gateway/values.yaml top to bottom before deploying — in particular:
replicaCount: 1/autoscaling.enabled: falseby default. The relational backend is SQLite + WAL (single writer) until the PostgreSQL backend (#1194) ships — see §6 below for the measured ceiling this implies. Don't raisereplicaCountpast 1 against the defaultReadWriteOncePVC.secret.create: falseby default. Setsecret.create=truewithsecret.jwtSecret/secret.policySigningKey/secret.githubWebhookSecretvia--setor a values override file (never commit real secret values), or pre-create a Secret matchingsecret.existingSecretyourself.networkPolicy.enabled: trueby default, restricting ingress to pods labeledaegis-client: "true". Add your ingress controller / SDK-running namespaces vianetworkPolicy.allowedNamespaceSelectors/allowedPodSelectors.serviceMonitor.enabled: falseby default — turn on if you run prometheus-operator; the chart self-suppresses the template if themonitoring.coreos.com/v1CRD isn't present in-cluster.- Cedar policy hot-reload is on by default (
AEGIS_POLICY_HOT_RELOAD=true). Ahelm upgrade --set cedarPolicy="$(cat my-policy.cedar)"updates the ConfigMap; kubelet refreshes the mounted file, and the gateway's filesystem watcher (#883) picks it up live without a pod restart.
Verify the rollout:
kubectl rollout status deployment/aegis-aegis-gateway
kubectl port-forward svc/aegis-aegis-gateway 8080:8080
curl http://127.0.0.1:8080/health
3. Bare metal¶
# Build a release binary
cargo build --release -p gateway --bin gateway
# binary lands at target/release/gateway (repo-root target/ — src/ is a
# workspace member, not its own workspace; see the root Cargo.toml)
# Run directly
CEDAR_POLICY_PATH=policies.cedar \
DATABASE_URL=sqlite://aegis.db \
AEGIS_BIND_ADDR=0.0.0.0:8080 \
./target/release/gateway
For a long-lived service, run it under systemd:
# /etc/systemd/system/aegis-gateway.service
[Unit]
Description=AegisAgent gateway
After=network.target
[Service]
Type=simple
User=aegis
WorkingDirectory=/opt/aegis
Environment=DATABASE_URL=sqlite:///opt/aegis/data/aegis.db
Environment=CEDAR_POLICY_PATH=/opt/aegis/policies.cedar
Environment=AEGIS_BIND_ADDR=0.0.0.0:8080
EnvironmentFile=-/etc/aegis/aegis.env
ExecStart=/opt/aegis/gateway
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/aegis/data
[Install]
WantedBy=multi-user.target
Put secret-bearing env vars (AEGIS_JWT_SECRET, AEGIS_POLICY_SIGNING_KEY, etc.) in /etc/aegis/aegis.env with 0600 permissions owned by the aegis user, not directly in the unit file (unit files are world-readable via systemctl cat).
sudo systemctl daemon-reload
sudo systemctl enable --now aegis-gateway
journalctl -u aegis-gateway -f
4. Environment variable reference¶
All variables are optional unless noted; the gateway runs with secure, fail-closed defaults when unset.
Core / networking¶
| Variable | Default | Description |
|---|---|---|
AEGIS_BIND_ADDR |
127.0.0.1:8080 |
REST listener. Set to 0.0.0.0:8080 in containers so the Service/proxy can reach the pod. |
AEGIS_GRPC_BIND_ADDR |
127.0.0.1:6334 |
gRPC listener. |
AEGIS_TLS_CERT / AEGIS_TLS_KEY |
unset | PEM cert/key paths. Both must be set together to enable TLS (same-port HTTP→HTTPS detection); either alone falls back to plain HTTP with a warning. |
AEGIS_CORS_ORIGINS |
unset (no CORS headers — most restrictive) | Comma-separated allowed origins. |
AEGIS_MAX_BODY_LIMIT_BYTES |
1048576 (1 MiB) |
Request body size cap. |
AEGIS_REQUEST_TIMEOUT_SECS |
30 |
Global per-request timeout. |
AEGIS_MAX_CONCURRENT_REQUESTS |
1000 |
Load-shed ceiling; requests beyond this get an immediate 503 instead of queuing. |
Database¶
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite://aegis.db |
SQLite connection string. |
AEGIS_DB_MAX_CONNECTIONS |
5 |
SQLite connection pool size. |
AEGIS_DB_IDLE_TIMEOUT_SECS |
30 |
Pool idle-connection timeout. |
AEGIS_DB_ACQUIRE_TIMEOUT_SECS |
5 |
Pool acquire timeout. |
AEGIS_DB_ENCRYPTION_KEY |
unset | Enables PRAGMA key (SQLCipher). Requires the gateway binary to be built with --features sqlcipher — fails closed at startup otherwise. |
Authentication & policy¶
| Variable | Default | Description |
|---|---|---|
AEGIS_JWT_REQUIRED |
false |
When true, every request must carry a valid JWT and AEGIS_JWT_SECRET must be set (startup error otherwise). |
AEGIS_JWT_SECRET |
unset | HMAC secret(s) for JWT validation; comma-separated for zero-downtime rotation ("new,old"). |
AEGIS_MTLS_CA_CERT |
unset | Enables mTLS — clients must present a cert signed by this CA. Requires TLS to also be configured. |
AEGIS_MTLS_CRL_PATH |
unset | CRL file for mTLS revocation checks (only used if AEGIS_MTLS_CA_CERT is set). |
CEDAR_POLICY_PATH |
policies.cedar |
Path to the Cedar policy file. |
AEGIS_POLICY_HOT_RELOAD |
false |
Watches CEDAR_POLICY_PATH for changes and reloads automatically. |
AEGIS_POLICY_SIGNING_KEY |
unset | Ed25519 verifying key for POST /v1/policies/bundles. Unset → that endpoint always returns 501. |
Observability¶
| Variable | Default | Description |
|---|---|---|
RUST_LOG |
info,gateway=debug,sqlx=info |
Standard tracing filter syntax. |
AEGIS_OTLP_ENDPOINT |
unset | Enables OTLP/HTTP export for both traces and the authorize_latency_seconds histogram / hash-mismatch and provenance-denial counters. Entirely inert when unset. |
AEGIS_SPLUNK_HEC_URL / AEGIS_SPLUNK_HEC_TOKEN |
unset | Both required to enable Splunk HTTP Event Collector export of audit events. |
AEGIS_SPLUNK_HEC_BATCH_INTERVAL_SECS |
30 |
Splunk export batch interval. |
AEGIS_BUILD_HASH |
unset | Surfaced for build/version attribution in logs; set by CI/release tooling. |
Webhooks & integrations¶
| Variable | Default | Description |
|---|---|---|
AEGIS_GITHUB_WEBHOOK_SECRET |
unset | HMAC secret to verify X-Hub-Signature-256 on POST /v1/ingest (source: github_webhook). Unset → signature check skipped. |
AEGIS_GITHUB_APP_TOKEN |
unset | Enables GitHub PR deny-comments and "Aegis Security Gate" check runs. |
AEGIS_SLACK_SIGNING_SECRET |
unset | HMAC secret to verify X-Slack-Signature. Unset → POST /v1/callbacks/slack refuses all requests with 404. |
AEGIS_ADMISSION_WEBHOOK_URL |
unset | Pre-authorize admission webhook endpoint. |
AEGIS_ADMISSION_WEBHOOK_TIMEOUT_SECS |
5 |
Admission webhook call timeout. |
AEGIS_ADMISSION_WEBHOOK_FAIL_OPEN |
true (any value other than "false"/"0" counts as enabled) |
Whether an unreachable admission webhook allows or blocks the action. |
AEGIS_WEBHOOK_URL / AEGIS_WEBHOOK_SECRET |
unset | SOC alert notification webhook + its HMAC signing secret. |
AEGIS_WEBHOOK_FAILURE_THRESHOLD |
5 |
Consecutive delivery failures before the circuit breaker opens. |
AEGIS_WEBHOOK_COOLDOWN_SECS |
30 |
Circuit-breaker cooldown before retrying. |
Qdrant / semantic indexing¶
| Variable | Default | Description |
|---|---|---|
AEGIS_QDRANT_URL |
unset | Enables the semantic-indexing exporter. Unset → no Qdrant client constructed at all. |
AEGIS_QDRANT_API_KEY |
unset | Qdrant auth. |
AEGIS_QDRANT_COLLECTION |
aegis_audit_events |
Target collection name. |
AEGIS_EMBEDDING_STRATEGY |
api |
api (HTTP embedding endpoint) or local (requires the local-embeddings/fastembed Cargo feature). |
AEGIS_EMBEDDING_MODEL |
text-embedding-3-small |
Embedding model name. |
AEGIS_EMBEDDING_URL |
https://api.openai.com/v1/embeddings |
API-strategy embedding endpoint. |
AEGIS_EMBEDDING_KEY |
unset | API-strategy embedding auth key. |
AEGIS_EMBEDDING_DIMENSION |
1536 |
Vector dimension. |
Rate limiting, quotas & caches¶
| Variable | Default | Description |
|---|---|---|
AEGIS_RATE_LIMIT_CAPACITY |
100 |
Per-agent token-bucket capacity. |
AEGIS_RATE_LIMIT_REFILL_RATE |
10 |
Tokens/sec refill. |
AEGIS_QUOTA_LIMIT |
0 (disabled) |
Per-agent request quota. |
AEGIS_QUOTA_WINDOW_SECS |
86400 (24h) |
Quota window. |
AEGIS_APPROVAL_CALLBACK_IP_LIMIT |
10 |
Per-IP rate limit on approval-decision callbacks. |
AEGIS_APPROVAL_ATTEMPT_LIMIT |
5 |
Max failed (4xx) attempts per approval_id. |
AEGIS_APPROVAL_ATTEMPT_WINDOW_SECS |
3600 |
Window for the above. |
AEGIS_SKILL_CACHE_CAPACITY |
1024 |
Registered-action metadata LRU cache size; 0 disables. |
AEGIS_REPLAY_NONCE_CACHE_CAPACITY |
10000 |
Replay-protection nonce dedup cache size; 0 disables replay rejection. |
AEGIS_RISK_WEIGHTS_CACHE_TTL_SECS |
60 |
TTL for the per-tenant risk-weights cache. |
Maintenance jobs (leader-elected)¶
These only run on whichever gateway instance wins the SQLite advisory-lock leader election (safe with multiple replicas sharing one DB — though see §6 on why multiple replicas aren't currently recommended).
| Variable | Default | Description |
|---|---|---|
AEGIS_LEADER_ELECTION_INTERVAL_SECS |
5 |
Leader-election tick interval. |
AEGIS_LEADER_LEASE_SECS |
20 |
Leader lease duration. |
AEGIS_AUDIT_ARCHIVAL_INTERVAL_SECS |
86400 |
How often old audit_events rows move to the archive table. |
AEGIS_AUDIT_RETENTION_DAYS |
90 |
Live-table retention before archival. |
AEGIS_APPROVAL_CLEANUP_INTERVAL_SECS |
86400 |
How often stale approvals are deleted. |
AEGIS_APPROVAL_RETENTION_DAYS |
30 |
Approval retention before cleanup. |
AEGIS_VACUUM_INTERVAL_SECS |
86400 |
VACUUM interval (reclaims space from the deletes above). |
AEGIS_RECEIPT_INTEGRITY_INTERVAL_SECS |
3600 |
Receipt-chain integrity check interval. |
AEGIS_POOL_HEALTH_SAMPLE_INTERVAL_SECS |
30 |
DB pool acquire-latency sampling interval. |
AEGIS_HEARTBEAT_FLUSH_INTERVAL_SECS |
30 |
Agent last_seen_at heartbeat flush interval. |
AEGIS_AUDIT_BATCH_SIZE |
100 |
Rows buffered before a batched audit_events flush. |
AEGIS_AUDIT_BATCH_FLUSH_MS |
500 |
Max time before a partial batch flushes anyway. |
AEGIS_SOC_INCIDENT_DEDUP_WINDOW_SECS |
3600 |
SOC incident-correlation dedup window. |
AEGIS_SOC_AUTONOMY_LEVEL |
unset | Per-tenant SOC response autonomy override. |
AEGIS_BACKUP_DIR |
backups |
Directory for POST /v1/admin/backup output. |
AEGIS_DRAIN_TIMEOUT_SECS |
10 |
Graceful-shutdown drain timeout for the in-flight event sink. |
AEGIS_DEFERRED_WRITE_DRAIN_TIMEOUT_SECS |
5 |
Graceful-shutdown drain timeout for fire-and-forget background writes (best-effort; a slow drain doesn't hold up shutdown indefinitely). |
AEGIS_RECEIPT_SIGNING_KEY |
unset | Optional signing key for receipt signatures. |
AEGIS_NARRATOR |
unset | Enables Claude-backed SOC incident narration when set. |
For the full, always-current source of truth, grep
AEGIS_insrc/src/main.rsandlib/— this table is maintained by hand and may lag a brand-new env var by one release.
5. Production checklist¶
On a non-loopback
AEGIS_BIND_ADDR, the gateway checks three of these itself at startup and logs aWARNnaming whichever are missing (warn_on_incomplete_production_hardening,src/src/main.rs) — it's a reminder, not a fail-closed gate, since each has legitimate reasons to be absent in a given deployment.AEGIS_JWT_REQUIREDis the one exception: an unauthenticated public bind fails closed at startup (assert_bind_security), it doesn't just warn.
- [ ] TLS: set
AEGIS_TLS_CERT/AEGIS_TLS_KEY, or terminate TLS at a reverse proxy / Kubernetes ingress in front of the gateway. Plain HTTP is fine for127.0.0.1-bound local dev only. (startup-warned) - [ ] Secrets: set
AEGIS_JWT_REQUIRED=truewith a realAEGIS_JWT_SECRET(notdefault_secret) before exposing the gateway beyond localhost. SetAEGIS_POLICY_SIGNING_KEYif you intend to use signed policy bundles. Never commit secret values — use your platform's secret store (KubernetesSecret, systemdEnvironmentFile, etc.). (startup fail-closed) - [ ] Admin key: set
AEGIS_ADMIN_API_KEYbefore going beyond localhost — without it, admin/debug/metrics endpoints stay disabled by default (safe, but you lose access to them too). (startup-warned) - [ ] Replay store: set
AEGIS_REPLAY_STORE=dbfor any multi-replica deployment — the default per-process in-memory replay-nonce cache doesn't dedupe across instances. (startup-warned) - [ ] Database encryption at rest: if required by your compliance posture, build with
--features sqlcipherand setAEGIS_DB_ENCRYPTION_KEY— the gateway fails closed at startup if the key is set without the matching build feature. - [ ] Monitoring: point
AEGIS_OTLP_ENDPOINTat your collector for traces + metrics, or scrape/metrics(Prometheus text) directly — bound on the same listener, not separately exposed. Wire up the Helm chart'sServiceMonitorif you run prometheus-operator. - [ ] Health probes:
/livez,/readyz,/startupzare already wired into the Helm chart's Deployment; if deploying elsewhere, point your orchestrator's liveness/readiness checks at them directly rather than/health(which does a DB round-trip and is heavier). - [ ] Backups: schedule
POST /v1/admin/backup, which writes a consistent point-in-time copy via SQLite'sVACUUM INTO(safe against a live database, no downtime) intoAEGIS_BACKUP_DIR. See Runbook: Backup and Restore for the restore procedure — there is no restore API, only a documented manual procedure. Not startup-checked:AEGIS_BACKUP_DIRalways resolves to a value (defaults tobackups), so its presence can't distinguish "backups are actually scheduled" from "nobody configured this." - [ ] CORS: leave
AEGIS_CORS_ORIGINSunset (no CORS headers) unless a browser-based dashboard or client genuinely needs cross-origin access; set it to an explicit allowlist, never a wildcard, if you do. - [ ] Rate limits & quotas: the defaults (
AEGIS_RATE_LIMIT_CAPACITY=100, refill10/s) are tuned for development. Size them per-agent based on your actual expected call volume before going live.
6. Capacity planning¶
Don't size for "N agents" or "M events/sec" in the abstract — size for the one thing that actually bottlenecks /v1/authorize: SQLite's single-writer serialization, measured in docs/performance-baseline.md:
| Constant-rate load | Sustained throughput | p50 | p95 | p99 | Errors |
|---|---|---|---|---|---|
| 100 req/s offered | 92 req/s | 3.2 ms | 4.0 ms | 5.7 ms | 0 |
| 150 req/s offered | 128 req/s | 3.0 ms | 4.0 ms | 5.4 ms | 1 (0.01%) |
| 200 req/s offered | 141 req/s | 3.1 ms | 4.6 ms | 9.6 ms | 1 (0.01%) |
| 1,000 req/s offered | 364 req/s | 5.0 s | 26.6 s | 29.9 s | 30,701 (51%) |
Practical reading: on hardware comparable to the benchmark (Intel Xeon Gold 6230R, 15 GiB RAM), a single gateway instance sustains ~130–150 /v1/authorize req/s with excellent latency. Past that, requests queue behind SQLite's write lock faster than the two synchronous writes per request (decisions + audit_events) can drain, and latency falls off a cliff rather than degrading gracefully — plan capacity with headroom below that ceiling, not up to it.
This is a property of the storage backend, not of CPU/memory/replica count — adding more gateway replicas in front of the same SQLite file does not raise this ceiling (they'd all serialize on the same WAL writer lock), which is why the Helm chart defaults to replicaCount: 1. If your expected load is within the ~100 req/s range, a single instance sized at the chart's default resources (100m/128Mi requests, 500m/512Mi limits) is sufficient headroom. If you need materially more throughput, the PostgreSQL backend (#1194) is the tracked path to MVCC-based concurrent writers and is the right point to revisit both replicaCount and the HPA — not before.
7. Security and Trust Boundaries¶
- Require TLS and authenticated tenant identity before binding beyond loopback.
- Restrict
/metrics, admin, debug, backup, policy, and containment surfaces with the documented guards. - Store JWT, webhook, database-encryption, KMS, and signing secrets outside manifests and shell history.
- Set admission-webhook failure behavior deliberately. A fail-open webhook may be acceptable only when it is advisory and deterministic Aegis policy still protects the action; use fail-closed behavior when the webhook is a required control.
- Keep NetworkPolicy default-deny and allow only agent workloads, ingress, storage, telemetry, and required integrations.
- Treat the SQLite file, backups, receipt signing material, and Cedar policy as security assets.
- Do not claim unknown-agent containment unless cage, sensor, egress, broker, ban, and quarantine force paths are deployed and verified.
8. Operations, Rollback, and Disaster Recovery¶
Before rollout, record the current image digest, policy version/hash, schema version, configuration, and receipt-chain head. Use startup/readiness probes and shift traffic only after a functional authorization and receipt verification.
For a canary, use an independent database or dry-run/mirrored requests; do not let two incompatible versions write one SQLite file. Roll back the image and compatible policy together. Database rollback means restoring a tested backup with the gateway stopped, then running readiness, tenant spot checks, and receipt verification before reopening traffic.
Exercise the Backup and Restore, Receipt Chain Verification, and Secret Rotation runbooks. Publish only RPO/RTO values demonstrated on deployment-class infrastructure.