Get started on Kubernetes (Helm)
The Helm chart at deploy/helm/olivares deploys the control
plane with the same secure defaults as every other path: a core
StatefulSet (non-root, read-only root filesystem, all capabilities dropped),
TLS on, loopback-equivalent exposure (a ClusterIP Service — nothing public
until you decide), and no default credentials. From there you opt into
Postgres, active-passive HA, scheduled encrypted DR backups
and the collectors DaemonSet for distributed ingest.
Everything below was rendered and checked against the chart in this
repository (helm lint + helm template, including the HA and backup
configurations and the chart’s own guard rails). The chart requires
Kubernetes ≥ 1.25.
1. The default install (single node, SQLite)
Section titled “1. The default install (single node, SQLite)”-
Install from the checkout:
Terminal window helm install olivares deploy/helm/olivaresThis renders exactly three workload objects — a
StatefulSet(1 replica, SQLite on a persistent 8 Gi PVC), aClusterIPService(8443 HTTPS, 8444 gRPC) and aServiceAccountwith no Kubernetes API privileges. Probes hit/readyz(readiness — drains when the store is down or the replica is not leader) and/livez(liveness — process only, never a dependency, so a store outage can never cause a restart loop). -
Read the one-time setup token from the pod log (it is printed to stdout only — in Kubernetes, that is the container log):
Terminal window kubectl logs -l app.kubernetes.io/component=core | sed -n '/FIRST-BOOT SETUP/,/========================/p' -
Port-forward and complete the first-run setup (the same flow as everywhere):
Terminal window # The Service is named <release>-olivares-core:kubectl port-forward svc/olivares-core 8443:8443curl -ksf -X POST https://127.0.0.1:8443/v1/setup \-H 'Content-Type: application/json' \-d '{"token":"<olst_ token>","email":"you@example.com","password":"<strong-password>"}'
Alternatively, with no Helm at all, the repository ships a flat manifest with the same safe single-node defaults, regenerated from the chart by CI:
kubectl apply -f deploy/manifests/install.yamlFor Argo CD / Flux / Kustomize (kustomize build --enable-helm), see
deploy/gitops/ — declarative wrappers over this same chart.
2. Postgres (multi-tenant)
Section titled “2. Postgres (multi-tenant)”The engine requires a least-privilege database role: LOGIN, no
superuser, no BYPASSRLS — row-level security is the tenant backstop, and
the engine refuses to start against a privileged role. Provision the role with
the canonical deploy/postgres/01-app-role.sql (the chart can run it for you
as a pre-install Job), put the DSN in a Secret, and point the chart at it:
kubectl create secret generic olivares-pg \ --from-literal=dsn='postgres://olivares_app:***@db:5432/olivares?sslmode=verify-full' \ --from-literal=admin-dsn='postgres://olivares_admin:***@db:5432/olivares?sslmode=verify-full'
helm install olivares deploy/helm/olivares \ --set core.engine=postgres \ --set postgres.dsnSecret=olivares-pg \ --set postgres.adminDsnKey=admin-dsnThe admin-dsn key holds a dedicated NOSUPERUSER BYPASSRLS role. It is
optional only if you never enable backups: it is what makes cross-tenant
system reads complete (the org list, multi-tenant coverage), and enabling
backup REQUIRES it — see §4. Creating it now
means the backup step later is a one-line change.
3. Active-passive HA
Section titled “3. Active-passive HA”With Postgres as the shared store, run several replicas. One is elected
leader (a Postgres advisory lock); standbys answer /readyz with
503 {"status":"standby"} so the Service only routes to the leader, and a
standby takes over automatically when the leader goes away.
Two things are structurally required, and the chart enforces both (we verified the guards render as hard failures, not warnings):
| Requirement | Why | Chart guard if missing |
|---|---|---|
| core.engine=postgres | The SQLite file is local to one pod — it cannot be a shared store. | core.replicaCount > 1 requires core.engine=postgres … |
| core.auditSigningKeySecret | Every replica must sign the audit ledger with the same Ed25519 key, or the hash chain forks on failover. | core.replicaCount > 1 requires core.auditSigningKeySecret … |
kubectl create secret generic olivares-audit-key \ --from-file=audit-signing.key=<base64-ed25519-private-key-file>
helm install olivares deploy/helm/olivares \ --set core.engine=postgres \ --set postgres.dsnSecret=olivares-pg \ --set core.replicaCount=3 \ --set core.auditSigningKeySecret=olivares-audit-keyThe canonical HA sizes are 3 or 5 replicas, active-passive. This single-writer design is deliberate (it matches the product’s consistency model); the production-readiness numbers document the availability tiers it supports.
4. Scheduled encrypted backups
Section titled “4. Scheduled encrypted backups”The chart ships a CronJob that runs olivares dr backup — an encrypted,
ledger-continuity-safe bundle (store snapshot + signing keys sealed under
your KEK + per-tenant chain-tip manifest):
kubectl create secret generic dr-kek --from-literal=passphrase='<strong passphrase>'
helm upgrade olivares deploy/helm/olivares --reuse-values \ --set backup.enabled=true \ --set backup.kekSecret=dr-kek \ --set postgres.adminDsnKey=admin-dsn # Postgres only, and REQUIRED with backupsOn Postgres the chart refuses to render without postgres.adminDsnKey: the
backup’s pg_dump keeps row_security=off and aborts as the application role
under FORCE ROW LEVEL SECURITY, so a CronJob wired to the app DSN would fail
every run and leave you with no backup at all.
Defaults: every 6 hours (backup.schedule sets your RPO), 14-day local
retention, a dedicated destination PVC. Mirror the destination offsite and
keep the KEK separate from the bundles — a same-cluster backup is not
disaster recovery. Drill restores with olivares dr verify; the full
procedure is in backup & restore.
5. Collectors DaemonSet (distributed ingest)
Section titled “5. Collectors DaemonSet (distributed ingest)”For the distributed topology, a collectors DaemonSet runs source connectors
on every node and pushes observations to the core over gRPC — collectors
have no inbound listener. The chart requires an ingest token and accepts
optional mTLS material:
helm upgrade olivares deploy/helm/olivares --reuse-values \ --set collectors.enabled=true \ --set collectors.ingestTokenSecret=olivares-ingest-token # ingest:write bearer tokenGive collectors their sources via collectors.sourcesConfig (the same
sources JSON as everywhere), and turn on
verified-client-cert mTLS for the collector→core channel with
tls.grpcClientCaSecret — see hardening.
Monitoring
Section titled “Monitoring”Set serviceMonitor on if you run the Prometheus operator; either way the
engine exposes /metrics unauthenticated (it carries no tenant data) and the
repository ships ready-made alert rules. See
monitor with Prometheus.
Next steps
Section titled “Next steps”- Wire sources: connect a source and the connector guides.
- Failure drills: troubleshooting — failover behavior, backpressure, ledger verification.
- Air-gapped cluster? Get started in an air-gapped site.