Production Configuration Guide
Required Environment Variables
| Variable |
Description |
Example |
DATABASE_URL |
PostgreSQL connection string |
postgresql+asyncpg://user:pass@host:5432/zkp_identity |
REDIS_URL |
Redis connection string |
redis://redis:6379/0 |
JWT_SECRET_KEY |
JWT signing key (min 32 chars) |
<random 64-char string> |
ENCRYPTION_KEY |
AES-256-GCM key (64 hex chars) |
<random 64 hex chars> |
ISSUER_PRIVATE_KEY_HEX |
Ed25519 signing key (64 hex chars) |
<random 64 hex chars> |
DEBUG |
Must be false in production |
false |
DOCS_ENABLED |
Set true only if API docs needed |
false |
FRONTEND_URL |
Frontend origin for CORS |
https://app.zkprova.com |
CORS_ORIGINS |
Comma-separated allowed origins |
["https://app.zkprova.com"] |
API_BASE_URL |
Public API base URL |
https://api.zkprova.com/api/v1 |
Optional
| Variable |
Default |
Description |
SENTRY_DSN |
(empty) |
Sentry error tracking DSN |
SENTRY_ENVIRONMENT |
development |
Sentry environment tag |
SENTRY_TRACES_SAMPLE_RATE |
0.1 |
Sentry performance sampling (0.0–1.0) |
EMAIL_BACKEND |
console |
smtp, sendgrid, or ses for production |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASSWORD |
— |
Required if EMAIL_BACKEND=smtp |
SENDGRID_API_KEY |
— |
Required if EMAIL_BACKEND=sendgrid |
DID_WEB_DOMAIN |
(empty) |
Domain for did:web identifiers |
METRICS_ENABLED |
true |
Expose /metrics for Prometheus |
WORKERS |
2 |
Uvicorn worker count (Docker CMD) |
PORT |
8000 |
Server port |
Database Pool Sizing
| Concurrency |
DB_POOL_SIZE |
DB_MAX_OVERFLOW |
Notes |
| Low (<100 rps) |
10 |
10 |
Dev/staging |
| Medium (100–500 rps) |
20 |
20 |
Default production |
| High (500+ rps) |
30–50 |
30 |
Ensure RDS max_connections supports it |
Additional pool settings:
- DB_POOL_TIMEOUT=30 — seconds to wait for a connection from the pool
- DB_POOL_RECYCLE=3600 — recycle connections every hour (avoids stale connections behind load balancers)
Uvicorn Worker Count
Rule of thumb: 2 * CPU_CORES + 1
| vCPUs |
Workers |
| 1 |
2 |
| 2 |
4 |
| 4 |
8 |
Set via the WORKERS environment variable in Docker.
Redis Configuration
- Use Redis 7+ with TLS in production
- Connection string:
rediss://user:password@host:6379/0 (note rediss:// for TLS)
- Used for: rate limiting (slowapi), session caching
- Recommended: ElastiCache with automatic failover
Sentry Setup
- Create a Sentry project (Python / FastAPI)
- Set
SENTRY_DSN to the project DSN
- Set
SENTRY_ENVIRONMENT=production
- Set
SENTRY_TRACES_SAMPLE_RATE=0.05 (5% in production to control cost)
- PII sending is disabled by default (
send_default_pii=False)
Health Checks
| Endpoint |
Purpose |
Use |
GET /health/live |
Liveness probe |
K8s livenessProbe |
GET /health/ready |
Readiness probe (checks DB + Redis) |
K8s readinessProbe |
GET /health |
Legacy simple health check |
Load balancer |
Backup Strategy
- RDS: Enable automated backups with 7-day retention
- Point-in-time recovery: Enabled by default with RDS
- Manual snapshots: Before major migrations
- Cross-region: Consider cross-region read replicas for DR
Monitoring Checklist
Prometheus Alerts (recommended)
- High error rate: 5xx responses > 1% over 5 minutes
- High latency: p99 response time > 2s
- DB pool exhaustion: pool wait time > 10s
- Circuit breaker open: email or push notification circuit open
- Rate limiting: high rate of 429 responses
- Disk usage: > 80% on any volume
- Memory usage: > 85% container memory
Grafana Dashboards
- Request rate, latency, and error rate by endpoint
- Database connection pool utilization
- Redis hit/miss ratio
- Active users and credential issuance rate
Security Checklist
- [ ]
DEBUG=false
- [ ]
DOCS_ENABLED=false (or restrict via network policy)
- [ ] Non-dev JWT secret and encryption key
- [ ] CORS origins restricted to production domains
- [ ] HTTPS termination at load balancer / ingress
- [ ] Container runs as non-root user
- [ ] Request body size limit enforced (1 MB default)
- [ ] Security headers middleware active (CSP, HSTS, X-Frame-Options)
- [ ] Rate limiting configured appropriately
- [ ] Sentry configured with
send_default_pii=False