Skip to main content

Deploying to Production

This guide covers running the SochDB v2 server in production: the server binary and its flags, ports, Docker images, the Helm chart, and Kubernetes health probes.

Versions

This page targets core engine 2.0.3 (the sochdb-grpc-server binary, the Helm chart appVersion, and the published Docker image). The language SDKs version independently (Python 0.5.9, Node.js 0.5.3, Go 0.4.5).

License

The core engine (Rust workspace, the sochdb crate, the gRPC server, and the MCP server) is AGPL-3.0-or-later, with commercial licensing available. The language SDKs (Python, Node.js, Go) are Apache-2.0. Deploying the server means you are running AGPL software; review the terms for your use case.


The server binary

The production server is a single binary, sochdb-grpc-server (crate sochdb-grpc). Its tagline is "Thick Server / Thin Client" — the server holds the storage engine, indexes, auth, and protocol gateways; clients are thin.

Build it from source:

cargo build --release --package sochdb-grpc --bin sochdb-grpc-server
# -> target/release/sochdb-grpc-server

Run it (binds loopback by default):

sochdb-grpc-server --host 0.0.0.0 --port 50051
make server-run and sochdb-server-config.toml are stale

The repository's make server-run target invokes the server with --config sochdb-server-config.toml, and that TOML declares listen_addr = "127.0.0.1:9600". Neither works. The current sochdb-grpc-server has no --config flag (clap will reject it) and never listens on port 9600. The server is configured entirely through CLI flags and environment variables, and the gRPC port defaults to 50051. Ignore the TOML config file and the make server-run shortcut.


CLI flags

All flags live on the server's Args struct. Defaults shown are the in-code defaults.

FlagDefaultEnv varPurpose
--host127.0.0.1Bind address (use 0.0.0.0 for remote access)
-p, --port50051gRPC port
-d, --debugoffDebug logging
--metrics-port9090Prometheus HTTP /metrics port (0 disables)
--ws-port8080WebSocket gateway port (0 disables)
--pg-port5433PostgreSQL wire protocol port (0 disables)
--authoffEnable gRPC authentication (API key + JWT)
--api-keynoneSOCHDB_API_KEYRegister an API key (requires --auth)
--tls-certnoneSOCHDB_TLS_CERTTLS cert PEM path (presence enables TLS)
--tls-keynoneSOCHDB_TLS_KEYTLS private key PEM path
--tls-canoneSOCHDB_TLS_CACA cert for mTLS client verification
--secrets-pathnoneSOCHDB_SECRETS_PATHKubernetes Secrets mount path
--pg-data-dirnoneSOCHDB_PG_DATA_DIRPersistent dir for real SQL over the PG wire

Additional environment variables read directly (not flags): SOCHDB_DATA_DIR (data directory), SOCHDB_API_KEY_PEPPER, SOCHDB_JWT_SECRET, SOCHDB_ENCRYPTION_KEY, and SOCHDB_API_KEYS (comma-separated). See Security and gateway caveats below for how each is used.

Default ports

ServiceDefault portNotes
gRPC50051Primary protocol; gRPC health service mounted here (no auth)
Prometheus metrics9090HTTP GET /metrics and GET /health
WebSocket gateway8080JSON message protocol at ws://<host>:8080/
PostgreSQL wire5433Simple-query protocol; see caveats below
gRPC-Web (Envoy)8080 (admin 9901)Only via the web docker-compose profile
Grafana3000Only via the monitoring docker-compose profile

Set any gateway port to 0 to disable that listener entirely.


Security and gateway caveats

These are operational realities you must account for before exposing the server.

PostgreSQL wire protocol is unauthenticated

The PG-wire listener (--pg-port, default 5433) implements simple-query protocol only (no extended/prepared statements), has no SSL/TLS (cleartext), and uses trust auth (no password). Without --pg-data-dir it runs an echo executor that only reflects queries back. To get real SQL (SELECT/INSERT/UPDATE/DELETE/DDL incl. JOINs) you must pass --pg-data-dir. Keep this listener on loopback unless you explicitly accept the risk — the server logs a loud warning if --host is non-loopback while PG-wire is enabled. Disable it in production with --pg-port 0 unless you need it.

At-rest encryption is a library API, not a server flag

SochDB ships an EncryptionEngine implementing AES-256-GCM-SIV (32-byte key, nonce-misuse-resistant AEAD) for data blocks, WAL entries, and checkpoint files. However, there is no CLI flag to enable at-rest encryption in sochdb-grpc-server, and the binary's main path does not construct or install an encryption engine. Treat at-rest encryption as an available library capability / planned wiring, not a runtime toggle today.

JWT is validation-only; API keys are hashed

The server validates JWTs (HS256) but does not issue them — tokens must be minted by an external IdP or by the caller. API keys are stored as SHA-256(key) (or HMAC-SHA256(pepper, key) when SOCHDB_API_KEY_PEPPER is set), never in plaintext. (Argon2 is used only for user passwords, not API keys.) RBAC roles are Owner / Editor / Viewer. Capabilities are presented over gRPC metadata as authorization: Bearer <token> or x-api-key: <key>.

CDC subscription where_predicate is not yet enforced

The gRPC SubscriptionService enforces table and operation-type filtering, but the where_predicate field is accepted and currently ignored — SQL WHERE filtering is not yet applied in the streaming loop. Do not rely on it for security or correctness.


Connection URI convention

SochDB documents a unified connect() URI scheme intended to select a transport from a single string:

URITransport
file://./dataEmbedded, on-disk (in-process)
ipc:///tmp/sochdb.sockLocal IPC over a Unix domain socket
grpc://localhost:50051gRPC (plaintext)
grpcs://prod.example.com:443gRPC over TLS (e.g. behind an ingress on 443)
Documented convention vs. in-repo SDKs

This URI mapping is described in the CHANGELOG and deployment docs. The SDKs currently shipped in this repository still use transport-specific clients that take a host:port (e.g. the Python and Go gRPC clients), not a single connect(uri) parser. Use the SDK's documented client constructor for now and treat the URI scheme as the deployment-level convention.


Docker

The official Dockerfile is a multi-stage build:

  • Builder: rust:1.91-bookworm (installs protobuf-compiler and cmake), builds --release --package sochdb-grpc --bin sochdb-grpc-server.
  • Runtime: debian:bookworm-slim, runs as a non-root user sochdb, data dir /var/lib/sochdb, EXPOSE 50051.
  • Health: grpc_health_probe (from ghcr.io/grpc-ecosystem/grpc-health-probe) is copied to /bin/grpc_health_probe and used as the Docker HEALTHCHECK.
# Runtime stage (abridged from docker/Dockerfile)
FROM debian:bookworm-slim AS runtime
# ... copy binary and grpc_health_probe ...
USER sochdb
EXPOSE 50051
ENV RUST_LOG=info
ENV SOCHDB_DATA_DIR=/var/lib/sochdb
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD grpc_health_probe -addr=:50051 || exit 1
ENTRYPOINT ["sochdb-grpc-server"]
CMD ["--host", "0.0.0.0", "--port", "50051"]

Run it:

docker run -p 50051:50051 -v sochdb-data:/var/lib/sochdb \
sochdb/sochdb-grpc:latest
Image name is inconsistent across the repo

The same image is referenced under several names: the Dockerfile and docker-compose tag it sochdb/sochdb-grpc:latest, the QUICKSTART pulls sushanth53/sochdb:latest, the Helm chart defaults to ghcr.io/sochdb/sochdb, and the agentslab preset uses docker.io/sochdb/sochdb-grpc:latest. Pick one registry/name for your deployment and use it consistently. This guide uses sochdb/sochdb-grpc:latest to match the build and compose files; the published pull command at time of writing is docker pull sushanth53/sochdb:latest.

docker-compose profiles

The compose file defines the base sochdb service (port 50051, volume sochdb-data:/var/lib/sochdb, limited to 2 CPU / 4G) plus opt-in profiles:

ProfileBrings upPorts
devA debug build of the server50052
webEnvoy gRPC-Web proxy8080 (admin 9901)
monitoringPrometheus + Grafana9090, 3000
docker compose up                              # base sochdb service only
docker compose --profile monitoring up # + Prometheus + Grafana
docker compose --profile web up # + Envoy gRPC-Web proxy

A minimal Alpine image (docker/Dockerfile.slim) and a docker-compose.production.yml are also provided.


Helm chart

The chart lives at deploy/helm/sochdb/ (chart name sochdb, version and appVersion 2.0.3, license AGPL-3.0-or-later).

Workload shape

The server runs as a StatefulSet (not a Deployment): ordered ready rollout, a headless service, terminationGracePeriodSeconds: 120, and a volumeClaimTemplate that mounts persistent data at /data.

  • Container ports: grpc (= service.grpcPort), http (= service.httpPort), and metrics (= observability.metrics.port, when enabled).
  • Injected env: SOCHDB_DATA_DIR=/data, SOCHDB_GRPC_PORT, SOCHDB_HTTP_PORT, SOCHDB_DURABILITY_LEVEL, SOCHDB_BOOT_RECOVERY_MODE, the SOCHDB_BOOT_{INIT,MIGRATE,RECOVER,WARMUP}_BUDGET_SECS budgets, SOCHDB_ADMISSION_QUEUE_DEPTH_REJECTION, POD_NAME, POD_NAMESPACE, plus any extraEnv.
  • An optional fs-check initContainer (busybox) verifies /data is writable and checks for /data/wal/.corrupted.
  • An optional log-shipper sidecar; readOnlyRootFilesystem with a /tmp emptyDir and a read-only config mount at /etc/sochdb.

values.yaml production defaults

image:
repository: ghcr.io/sochdb/sochdb # tag defaults to appVersion (2.0.3)
replicaCount: 1

resources:
requests: { cpu: 1000m, memory: 2Gi }
limits: { cpu: 4000m, memory: 2Gi }

persistence:
size: 50Gi
accessModes: [ReadWriteOnce]

service:
type: ClusterIP
grpcPort: 50051
httpPort: 8080

# Boot-FSM-aligned probes (TCP socket on 50051 by default)
# startup: failureThreshold 60 x 10s = up to 10 min (WAL replay / migrations)
# readiness: period 5s
# liveness: initialDelaySeconds 30

boot:
recoveryMode: normal
budgets: { initSeconds: 30, migrateSeconds: 120, recoverSeconds: 300, warmupSeconds: 60 }

durability:
level: group_commit # maxBatch 128, flushIntervalMs 10; WAL archiving off

checkpoint:
mode: incremental # walSizeTriggerMB 128, maxDurationSeconds 30

admissionControl:
queueDepthWarning: 100
queueDepthRejection: 1000

security:
tls: { enabled: false } # certSecretName / mtls / caSecretName
auth: { enabled: false } # JWT/JWKS: jwksUrl / issuer
encryption: { enabled: false } # data-at-rest (key from secrets)

networkPolicy: { enabled: true }
podDisruptionBudget: { enabled: true, minAvailable: 0 } # single-node maintenance
observability:
metrics: { port: 9090, path: /metrics, serviceMonitor: { enabled: false } }

Install:

helm install sochdb deploy/helm/sochdb/

Presets

Two value overlays are bundled:

  • values-agentslab.yaml (production, MicroK8s): image docker.io/sochdb/sochdb-grpc:latest with pullPolicy: Never; gRPC ingress enabled (className public, host sochdb.agentslab.host, TLS via cert-manager letsencrypt-prod); httpEnabled: false to avoid a host/path conflict with the gRPC ingress; 10Gi persistence on microk8s-hostpath; runs as root (runAsUser: 0, readOnlyRootFilesystem: false) for hostpath; networkPolicy and the fs-check initContainer disabled.

    helm install sochdb deploy/helm/sochdb/ -f deploy/helm/sochdb/values-agentslab.yaml
  • values-minikube.yaml (local dev): image sochdb/sochdb-grpc:2.0.3 with pullPolicy: Never; small resources; 2Gi persistence; service NodePort; probes switched to HTTP GET /metrics on port 9090 (TCP-socket probe nulled) to avoid gRPC h2 probe noise; extraEnv RUST_LOG=debug and SOCHDB_DISABLE_ANALYTICS=true.

    helm install sochdb deploy/helm/sochdb/ -f deploy/helm/sochdb/values-minikube.yaml

gRPC ingress

The chart creates a gRPC Ingress (<fullname>-grpc, nginx) with backend-protocol: "GRPC", ssl-redirect: "true", proxy-body-size: "0", and 1-hour read/send timeouts; cert-manager cluster-issuer is wired when tls.certManager is set, terminating into the <fullname>-grpc-tls secret and routing / to the gRPC port. A second HTTP Ingress (<fullname>-http) is created only when ingress.httpEnabled is true.


Kubernetes health probes

In-cluster probes default to a TCP socket on 50051 for startup, readiness, and liveness. They are aligned with the server's Boot FSM: the startup probe tolerates up to 10 minutes (60 failures x 10s) so WAL replay and migrations can complete before the pod is marked failed.

  • The gRPC health service is mounted on the gRPC port and is not behind auth, so external probes can reach it. grpc_health_probe is used at the Docker HEALTHCHECK layer; the Helm probes use a plain TCP socket by default.
  • The minikube preset swaps the probes to HTTP GET /metrics:9090 to avoid noisy gRPC/h2 probe logs in local clusters.

A minimal raw-Kubernetes probe block (without the chart) looks like:

startupProbe:
tcpSocket: { port: 50051 }
periodSeconds: 10
failureThreshold: 60 # ~10 min for WAL replay / migrations
readinessProbe:
tcpSocket: { port: 50051 }
periodSeconds: 5
livenessProbe:
tcpSocket: { port: 50051 }
initialDelaySeconds: 30

systemd (bare metal / VM)

For non-containerized hosts, run the binary under systemd. Note the binary name is sochdb-grpc-server and it is configured via flags/env (no config file).

# /etc/systemd/system/sochdb.service
[Unit]
Description=SochDB gRPC Server
After=network.target
Documentation=https://github.com/sochdb/sochdb

[Service]
Type=simple
User=sochdb
Group=sochdb
ExecStart=/usr/local/bin/sochdb-grpc-server --host 0.0.0.0 --port 50051 --pg-port 0
Restart=always
RestartSec=5

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/sochdb

# Resource limits
LimitNOFILE=65536

# Environment
Environment=RUST_LOG=info
Environment=SOCHDB_DATA_DIR=/var/lib/sochdb
Environment=RUST_BACKTRACE=1

[Install]
WantedBy=multi-user.target
sudo useradd -r -s /bin/false sochdb
sudo mkdir -p /var/lib/sochdb && sudo chown -R sochdb:sochdb /var/lib/sochdb
sudo chmod 700 /var/lib/sochdb
sudo systemctl daemon-reload
sudo systemctl enable --now sochdb

Sizing and durability

These remain useful planning heuristics.

Sizing guidelines

Data sizeRAMDiskCPU
less than 10 GB2 GB30 GB SSD2 cores
10–100 GB8 GB300 GB NVMe4 cores
100 GB – 1 TB32 GB3 TB NVMe8 cores

Provision roughly 2x the expected data size in disk for compaction and checkpoint headroom, and prefer NVMe for write-heavy workloads.

Durability

The Helm chart exposes durability.level (default group_commit, batching up to 128 commits with a 10 ms flush interval) and checkpoint.mode (default incremental, triggered at 128 MB of WAL or 30 s). Tighten the batch/flush settings for lower latency, or loosen them for higher throughput, depending on your durability requirements.


See also