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.
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).
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 staleThe 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.
| Flag | Default | Env var | Purpose |
|---|---|---|---|
--host | 127.0.0.1 | — | Bind address (use 0.0.0.0 for remote access) |
-p, --port | 50051 | — | gRPC port |
-d, --debug | off | — | Debug logging |
--metrics-port | 9090 | — | Prometheus HTTP /metrics port (0 disables) |
--ws-port | 8080 | — | WebSocket gateway port (0 disables) |
--pg-port | 5433 | — | PostgreSQL wire protocol port (0 disables) |
--auth | off | — | Enable gRPC authentication (API key + JWT) |
--api-key | none | SOCHDB_API_KEY | Register an API key (requires --auth) |
--tls-cert | none | SOCHDB_TLS_CERT | TLS cert PEM path (presence enables TLS) |
--tls-key | none | SOCHDB_TLS_KEY | TLS private key PEM path |
--tls-ca | none | SOCHDB_TLS_CA | CA cert for mTLS client verification |
--secrets-path | none | SOCHDB_SECRETS_PATH | Kubernetes Secrets mount path |
--pg-data-dir | none | SOCHDB_PG_DATA_DIR | Persistent 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
| Service | Default port | Notes |
|---|---|---|
| gRPC | 50051 | Primary protocol; gRPC health service mounted here (no auth) |
| Prometheus metrics | 9090 | HTTP GET /metrics and GET /health |
| WebSocket gateway | 8080 | JSON message protocol at ws://<host>:8080/ |
| PostgreSQL wire | 5433 | Simple-query protocol; see caveats below |
| gRPC-Web (Envoy) | 8080 (admin 9901) | Only via the web docker-compose profile |
| Grafana | 3000 | Only 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.
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.
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.
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>.
where_predicate is not yet enforcedThe 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:
| URI | Transport |
|---|---|
file://./data | Embedded, on-disk (in-process) |
ipc:///tmp/sochdb.sock | Local IPC over a Unix domain socket |
grpc://localhost:50051 | gRPC (plaintext) |
grpcs://prod.example.com:443 | gRPC over TLS (e.g. behind an ingress on 443) |
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(installsprotobuf-compilerandcmake), builds--release --package sochdb-grpc --bin sochdb-grpc-server. - Runtime:
debian:bookworm-slim, runs as a non-root usersochdb, data dir/var/lib/sochdb,EXPOSE 50051. - Health:
grpc_health_probe(fromghcr.io/grpc-ecosystem/grpc-health-probe) is copied to/bin/grpc_health_probeand used as the DockerHEALTHCHECK.
# 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
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:
| Profile | Brings up | Ports |
|---|---|---|
dev | A debug build of the server | 50052 |
web | Envoy gRPC-Web proxy | 8080 (admin 9901) |
monitoring | Prometheus + Grafana | 9090, 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), andmetrics(=observability.metrics.port, when enabled). - Injected env:
SOCHDB_DATA_DIR=/data,SOCHDB_GRPC_PORT,SOCHDB_HTTP_PORT,SOCHDB_DURABILITY_LEVEL,SOCHDB_BOOT_RECOVERY_MODE, theSOCHDB_BOOT_{INIT,MIGRATE,RECOVER,WARMUP}_BUDGET_SECSbudgets,SOCHDB_ADMISSION_QUEUE_DEPTH_REJECTION,POD_NAME,POD_NAMESPACE, plus anyextraEnv. - An optional
fs-checkinitContainer (busybox) verifies/datais writable and checks for/data/wal/.corrupted. - An optional
log-shippersidecar;readOnlyRootFilesystemwith a/tmpemptyDir 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): imagedocker.io/sochdb/sochdb-grpc:latestwithpullPolicy: Never; gRPC ingress enabled (classNamepublic, hostsochdb.agentslab.host, TLS via cert-managerletsencrypt-prod);httpEnabled: falseto avoid a host/path conflict with the gRPC ingress; 10Gi persistence onmicrok8s-hostpath; runs as root (runAsUser: 0,readOnlyRootFilesystem: false) for hostpath;networkPolicyand thefs-checkinitContainer disabled.helm install sochdb deploy/helm/sochdb/ -f deploy/helm/sochdb/values-agentslab.yaml -
values-minikube.yaml(local dev): imagesochdb/sochdb-grpc:2.0.3withpullPolicy: Never; small resources; 2Gi persistence; service NodePort; probes switched to HTTPGET /metricson port 9090 (TCP-socket probe nulled) to avoid gRPC h2 probe noise;extraEnvRUST_LOG=debugandSOCHDB_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_probeis used at the DockerHEALTHCHECKlayer; the Helm probes use a plain TCP socket by default. - The minikube preset swaps the probes to HTTP
GET /metrics:9090to 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 size | RAM | Disk | CPU |
|---|---|---|---|
| less than 10 GB | 2 GB | 30 GB SSD | 2 cores |
| 10–100 GB | 8 GB | 300 GB NVMe | 4 cores |
| 100 GB – 1 TB | 32 GB | 3 TB NVMe | 8 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
- Security and gateway caveats — auth, RBAC, TLS/mTLS, secrets, at-rest encryption status
- Kubernetes health probes — probe shape and the metrics/health endpoints
- Performance Guide — tuning
- Architecture — system design