---
title: Deployment
path: operations/deployment
status: published
---

# Deployment

ScaiVault is a stateless container plus a PostgreSQL database, a Redis cache, and a KMS. Everything else — object storage, DNS providers, federated backends — is optional.

## Prerequisites

- **PostgreSQL 14+.** Dedicated database with a dedicated user. Expect ~10 GB per 100k secrets including history.
- **Redis 7+.** Single instance is fine for low-volume deployments; cluster for HA.
- **A KMS.** AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, or a PKCS#11 HSM. ScaiVault stores only a wrapped root key locally.
- **ScaiKey.** An instance you control, or the managed `scaikey.scailabs.ai`.
- **Object storage (optional).** S3-compatible. Used for large artifacts (certs, CRLs, audit exports). In-DB fallback works but limits how large those artifacts can be.

## Configuration

All configuration is via environment variables.

### Core

| Variable | Required | Description |
|----------|----------|-------------|
| `DATABASE_URL` | Yes | `postgresql://user:pass@host:5432/scaivault?sslmode=require` |
| `REDIS_URL` | Yes | `redis://host:6379/0` or `rediss://` for TLS |
| `SCAIKEY_URL` | Yes | `https://scaikey.scailabs.ai` |
| `SCAIKEY_JWKS_URL` | No | Override auto-discovery |
| `BIND_ADDRESS` | No | Default `0.0.0.0:8443` |
| `BASE_URL` | Yes | Your public ScaiVault URL |
| `LOG_LEVEL` | No | `info` (default), `debug`, `warn`, `error` |
| `LOG_FORMAT` | No | `json` (default) or `text` |

### KMS

Pick one. Provider auth is via the cloud provider's standard mechanisms (IAM role, workload identity, etc.) unless you specify static credentials.

```bash
# AWS KMS
KMS_PROVIDER=aws
KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/abcd

# GCP KMS
KMS_PROVIDER=gcp
KMS_KEY_NAME=projects/acme/locations/global/keyRings/scaivault/cryptoKeys/root

# Azure Key Vault
KMS_PROVIDER=azure
KMS_KEY_URL=https://acme.vault.azure.net/keys/scaivault-root/abc

# HashiCorp Vault Transit
KMS_PROVIDER=hashicorp
KMS_ENDPOINT=https://vault.internal:8200
KMS_KEY_NAME=scaivault-root
KMS_AUTH_TOKEN_FILE=/var/run/secrets/vault-token

# PKCS#11 HSM
KMS_PROVIDER=pkcs11
KMS_LIBRARY=/usr/lib/softhsm/libsofthsm2.so
KMS_SLOT=0
KMS_LABEL=scaivault-root
KMS_PIN_FILE=/etc/scaivault/hsm-pin
```

### Object storage (optional)

```bash
STORAGE_PROVIDER=s3
STORAGE_BUCKET=scaivault-prod
STORAGE_REGION=us-east-1
# Static creds (optional; prefer IAM roles):
STORAGE_ACCESS_KEY_ID=...
STORAGE_SECRET_ACCESS_KEY=...
```

Providers: `s3`, `gcs`, `azure_blob`, `minio`.

### Identity cache sync

```bash
IDENTITY_SYNC_WEBHOOK_SECRET=<shared with ScaiKey>
IDENTITY_SYNC_INTERVAL=15m  # background full-sync interval
```

### Rate limits (optional override)

```yaml
RATE_LIMITS: |
  secrets:read:
    rate: 2000
    window: 1m
  secrets:write:
    rate: 200
    window: 1m
```

See [Rate Limiting](../advanced/rate-limiting) for the full list of categories.

## Single-node Docker

For development or small deployments:

```bash
docker run -d \
  -p 8443:8443 \
  -e DATABASE_URL=postgresql://... \
  -e REDIS_URL=redis://... \
  -e SCAIKEY_URL=https://scaikey.scailabs.ai \
  -e KMS_PROVIDER=aws \
  -e KMS_KEY_ID=arn:aws:kms:... \
  -e BASE_URL=https://scaivault.example.com \
  scailabs/scaivault:1.0.0
```

The container runs the API on port 8443 with TLS terminated upstream. For TLS at the container, mount certs and set `TLS_CERT_FILE` / `TLS_KEY_FILE`.

## Kubernetes

A minimal `Deployment`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: scaivault
spec:
  replicas: 3
  selector:
    matchLabels:
      app: scaivault
  template:
    metadata:
      labels:
        app: scaivault
    spec:
      serviceAccountName: scaivault  # with KMS IAM binding
      containers:
      - name: scaivault
        image: scailabs/scaivault:1.0.0
        ports:
        - containerPort: 8443
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef: {name: scaivault-db, key: url}
        - name: REDIS_URL
          valueFrom:
            secretKeyRef: {name: scaivault-redis, key: url}
        - name: SCAIKEY_URL
          value: https://scaikey.scailabs.ai
        - name: KMS_PROVIDER
          value: aws
        - name: KMS_KEY_ID
          value: arn:aws:kms:us-east-1:123:key/abc
        - name: BASE_URL
          value: https://scaivault.acme.example
        livenessProbe:
          httpGet:
            path: /v1/health
            port: 8443
            scheme: HTTPS
          initialDelaySeconds: 10
        readinessProbe:
          httpGet:
            path: /v1/health/ready
            port: 8443
            scheme: HTTPS
          periodSeconds: 5
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 2000m
            memory: 2Gi
```

3 replicas is a good starting point. Scale horizontally — ScaiVault instances are stateless.

## First-boot setup

On first boot, ScaiVault:

1. Runs database migrations.
2. Generates an initial encryption root (wrapped by your KMS).
3. Creates a bootstrap `super_admin` user as configured by `BOOTSTRAP_SUPER_ADMIN`.

For production, configure:

```bash
BOOTSTRAP_SUPER_ADMIN=user:admin@acme.example
```

First person signs in via ScaiKey with that email, gets promoted to `super_admin` automatically. After first login, set `BOOTSTRAP_SUPER_ADMIN=` (empty) to disable.

## Database migrations

On startup, ScaiVault checks migration state. If ahead of the schema it expects, it refuses to start. If behind, by default it migrates automatically. To run migrations as a separate step:

```bash
docker run --rm \
  -e DATABASE_URL=... \
  scailabs/scaivault:1.0.0 migrate
```

Then start the API with `--no-migrate` (or set `MIGRATE_ON_START=false`).

## TLS

ScaiVault expects TLS termination. Typical setups:

- **Load balancer terminates** (ALB, GLB, Traefik, nginx-ingress). ScaiVault listens HTTP internally.
- **ScaiVault terminates.** Set `TLS_CERT_FILE` and `TLS_KEY_FILE`. Certs from your PKI.
- **mTLS internal.** Additional `TLS_CLIENT_CA_FILE` forces client cert authentication on top of bearer tokens.

## Scaling

- **API pods:** stateless. Scale to taste; 3 is a sensible minimum.
- **Postgres:** single-writer, one or more replicas. ScaiVault can route reads to replicas with `DATABASE_URL_READ=postgresql://...`.
- **Redis:** single-instance works up to tens of thousands of concurrent identities; cluster mode beyond that.

## High availability

- Run at least 3 API pods across failure domains.
- Postgres streaming replication + automated failover (Patroni, Cloud SQL, RDS Multi-AZ).
- Redis Sentinel or cluster mode.
- KMS: use a regional key with cross-region replication; ScaiVault supports failover to a secondary key if the primary is unreachable (`KMS_KEY_ID_SECONDARY`).

## Backup and recovery

**What to back up:**

- PostgreSQL: full + WAL.
- Object store bucket (if used).
- KMS key policy (so you can restore access).

**What NOT to back up separately:**

- The plaintext root key — it never leaves the KMS.
- Application container state — everything interesting is in Postgres.

**Recovery test:** restore DB to a staging cluster, point at a test KMS key wrapped by the same rehydrated material, verify a secret reads correctly. Do this at least annually.

## What's next

- [Health and Monitoring](./health-and-monitoring)
- [Troubleshooting](./troubleshooting)
- [Architecture](../introduction/architecture)
