> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quantumapi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Run Qustody in development, staging, and production using Docker, Compose, or Kubernetes.

Qustody is distributed as a single Go binary plus a PostgreSQL database. Most production deployments run it as a container behind a load balancer, with managed Postgres (AWS RDS, Aurora, or equivalent) and a separate signing service.

## 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 at `custody/Dockerfile`:

```bash theme={null}
# Build
docker build -t qustody-api:latest -f custody/Dockerfile .

# Run
docker run --rm -p 8080:8080 -p 9090:9090 \
  -e CUSTODY_DB_HOST=postgres \
  -e CUSTODY_DB_PASSWORD=secret \
  -e CUSTODY_NODE_RPC_URL=http://quantum-chain:8545 \
  -e CUSTODY_AUTH_ENABLED=true \
  qustody-api:latest
```

The container exposes:

* **`8080`** — HTTP API
* **`9090`** — Prometheus metrics (`/metrics`)

## Local development with Docker Compose

A reference `docker-compose.yaml` lives at the repo root and brings up PostgreSQL, the Qustody API, and a development Quantum Chain node:

```bash theme={null}
docker compose up -d postgres api
docker compose logs -f api
```

The development profile uses `CUSTODY_AUTH_ENABLED=false` and `CUSTODY_RBAC_ENABLED=false` to make exploration easy. **Never deploy that combination.**

## Production deployment

### Recommended topology

```mermaid theme={null}
flowchart LR
    LB[Load balancer<br/>TLS termination] --> API1[Qustody API<br/>replica 1]
    LB --> API2[Qustody API<br/>replica 2]
    LB --> APIN[Qustody API<br/>replica N]
    API1 --> RDS[(Aurora PostgreSQL<br/>writer + reader)]
    API2 --> RDS
    APIN --> RDS
    API1 --> Signer[Signing service]
    API2 --> Signer
    APIN --> Signer
    API1 --> Node[Quantum Chain<br/>RPC pool]
    API2 --> Node
    APIN --> Node
    API1 -.-> Webhook[Customer webhooks]
```

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)                   |

See the full list at [Configuration reference](/reference/configuration).

### Aurora / read-replica configuration

```bash theme={null}
CUSTODY_DB_HOST=writer.cluster-xxx.us-east-1.rds.amazonaws.com
CUSTODY_DB_READ_REPLICA_ENABLED=true
CUSTODY_DB_READ_HOST=reader.cluster-ro-xxx.us-east-1.rds.amazonaws.com
CUSTODY_DB_IAM_AUTH=true
CUSTODY_DB_SSL_ROOT_CERT=/etc/ssl/rds-ca.pem
```

When `CUSTODY_DB_IAM_AUTH=true`, `CUSTODY_DB_PASSWORD` is not required — Qustody uses RDS IAM tokens.

### Signer configuration (gRPC mode)

```bash theme={null}
CUSTODY_SIGNER_TYPE=grpc
CUSTODY_SIGNER_BACKEND=enqlave    # or "qey"
CUSTODY_SIGNER_GRPC_TARGET=signer.internal:443
CUSTODY_SIGNER_GRPC_CLIENT_CERT=/etc/qustody/client.crt
CUSTODY_SIGNER_GRPC_CLIENT_KEY=/etc/qustody/client.key
CUSTODY_SIGNER_GRPC_CA_CERT=/etc/qustody/ca.crt
CUSTODY_SIGNER_GRPC_TIMEOUT=30s
```

For callback or remote modes see [External signing](/guides/external-signing).

### SSO

```bash theme={null}
CUSTODY_SSO_ENABLED=true
CUSTODY_SSO_JWT_SIGNING_KEY=<32 random bytes hex-encoded>
CUSTODY_SSO_ALLOWED_ISSUERS=https://login.example.com,https://accounts.google.com
CUSTODY_SSO_AUTO_PROVISION=true
CUSTODY_SSO_DEFAULT_ROLE=viewer
```

### Compliance / AML

```bash theme={null}
CUSTODY_COMPLIANCE_ENABLED=true
CUSTODY_COMPLIANCE_PROVIDER=shuftipro     # or "chainalysis", "noop"
CUSTODY_SHUFTIPRO_CLIENT_ID=...
CUSTODY_SHUFTIPRO_SECRET_KEY=...
CUSTODY_SHUFTIPRO_CALLBACK_URL=https://api.your-domain.com/v1/compliance/callback/shuftipro
```

## 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 |

In Kubernetes:

```yaml theme={null}
livenessProbe:
  httpGet: { path: /healthz, port: 8080 }
  periodSeconds: 10
readinessProbe:
  httpGet: { path: /readyz, port: 8080 }
  periodSeconds: 5
```

## 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`                                                          |

Enable rate limiting with Redis for distributed deployments:

```bash theme={null}
CUSTODY_RATE_LIMIT_ENABLED=true
CUSTODY_RATE_LIMIT_RPM=120
CUSTODY_RATE_LIMIT_BURST=20
CUSTODY_RATE_LIMIT_REDIS_URL=redis://redis:6379/0
```

## Database migrations

Migrations live in `custody/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_KEY` is set; back up the key separately.

## Related

* [Configuration reference](/reference/configuration)
* [Security model](/qustody/security-model)
* [Troubleshooting](/qustody/troubleshooting)
