Prerequisites
- Go 1.24+ if you build from source
- PostgreSQL 16+ (the schema is auto-migrated on startup when
CUSTODY_DB_MIGRATE_ON_START=true) - A reachable Quantum Chain RPC node (HTTP and optionally WebSocket)
- An external post-quantum signing service (callback HTTP, remote HTTP, or gRPC)
- TLS certificates for production (terminated at your load balancer or directly on the API)
Container image
A multi-stage Dockerfile is shipped atcustody/Dockerfile:
8080— HTTP API9090— Prometheus metrics (/metrics)
Local development with Docker Compose
A referencedocker-compose.yaml lives at the repo root and brings up PostgreSQL, the Qustody API, and a development Quantum Chain node:
CUSTODY_AUTH_ENABLED=false and CUSTODY_RBAC_ENABLED=false to make exploration easy. Never deploy that combination.
Production deployment
Recommended topology
Run at least two replicas behind a load balancer. The API is stateless; replicas coordinate through PostgreSQL row-level locks for transaction recovery.Required environment variables
| Variable | Description |
|---|---|
CUSTODY_DB_HOST, CUSTODY_DB_PORT, CUSTODY_DB_NAME, CUSTODY_DB_USER, CUSTODY_DB_PASSWORD | PostgreSQL connection |
CUSTODY_DB_SSLMODE | Set to verify-full in production |
CUSTODY_NODE_RPC_URL | Quantum Chain HTTP RPC endpoint |
CUSTODY_NODE_WS_URL | Quantum Chain WebSocket endpoint (for deposit monitoring) |
CUSTODY_AUTH_ENABLED | true |
CUSTODY_RBAC_ENABLED | true |
CUSTODY_WEBHOOK_SIGNING_KEY | Random 32-byte hex string |
CUSTODY_WEBHOOK_ENCRYPTION_KEY | 64-hex-char (32 byte) AES-256-GCM key for webhook secret encryption at rest |
CUSTODY_ENV | production (enforces HTTPS for tenant and webhook URLs) |
Aurora / read-replica configuration
CUSTODY_DB_IAM_AUTH=true, CUSTODY_DB_PASSWORD is not required — Qustody uses RDS IAM tokens.
Signer configuration (gRPC mode)
SSO
Compliance / AML
Health and readiness
The API exposes two endpoints used by load balancers and orchestrators:| Endpoint | Purpose |
|---|---|
GET /healthz | Liveness — returns 200 OK if the process is alive |
GET /readyz | Readiness — returns 200 OK only when DB and signer are reachable |
Observability
| Surface | Endpoint / sink |
|---|---|
| Metrics | :9090/metrics (Prometheus format) |
| Tracing | OpenTelemetry OTLP when CUSTODY_OTEL_ENABLED=true and CUSTODY_OTEL_ENDPOINT is set |
| Logs | Structured JSON to stdout |
| Health | GET /healthz, GET /readyz |
Database migrations
Migrations live incustody/store/postgres/migrations/. They are applied automatically on startup when CUSTODY_DB_MIGRATE_ON_START=true (default).
For zero-downtime upgrades, run migrations from a single instance with CUSTODY_DB_MIGRATE_ON_START=true and start the rest with false.
Backups and disaster recovery
- PostgreSQL is the source of truth. Use point-in-time recovery (PITR).
- Audit log integrity is verifiable independently via
GET /v1/audit/verify. - Webhook secrets are encrypted column-level when
CUSTODY_WEBHOOK_ENCRYPTION_KEYis set; back up the key separately.