Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ on:
branches: [main]
paths:
- 'installer/**'
- 'charts/**'
- 'tests/fixtures/governance/STUDIO_TAG'
- '.github/workflows/installer.yml'
pull_request:
paths:
- 'installer/**'
- 'charts/**'
- 'tests/fixtures/governance/STUDIO_TAG'
- '.github/workflows/installer.yml'

Expand Down Expand Up @@ -40,6 +42,18 @@ jobs:
run: bun run embed
env:
LIBREDB_STUDIO_REPO: ${{ github.workspace }}/studio-src
# The Helm chart's tests (test/unit/kubernetes/chart.test.ts) render the chart with a pinned Helm; its Studio
# dependency is the chart that embed just downloaded and checked against the release manifest's sha256.
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
with:
version: v3.21.3
- name: Lint the Helm chart
working-directory: .
run: |
mkdir -p charts/libredb-platform/charts
cp installer/generated/libredb-studio-*.tgz charts/libredb-platform/charts/
helm lint charts/libredb-platform --strict -f charts/libredb-platform/tests/e2e/values-kind.yaml \
--set platform.license.value=ldl1.0123456789abcdef.eyJpZCI6IkxJTlQifQ.c2lnbmF0dXJl
- run: bun run typecheck
- run: bun run coverage:check
- run: bun run build
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ next-env.d.ts
/installer/keycloak-export/
/installer/ca.crt
*.license

# Helm chart dependencies, built with helm dependency build from Chart.lock
charts/*/charts/*.tgz
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Write to [info@libredb.org](mailto:info@libredb.org) or join the waiting list at

Platform 1.0.1 is the current release, published as the image `ghcr.io/libredb/libredb-platform`, and pairs with LibreDB Studio 1.0.0.
It installs with Docker on one x86_64 Linux host: the installer deploys PostgreSQL, Keycloak, Platform, Studio and a Caddy edge, and can use an existing PostgreSQL server or Keycloak instead.
Kubernetes support follows in the next release.
On Kubernetes, the Helm chart [`charts/libredb-platform`](charts/libredb-platform) installs the same, as the [Kubernetes install guide](docs/deploy/install-kubernetes.md) describes.
Cloud mode serves development and the hosted service of a later release, and it is not a supported way to install LibreDB.
Download the installer from this repository's [GitHub releases](https://github.com/libredb/libredb-platform/releases) and follow the [Docker install guide](docs/deploy/install-docker.md).
Every install needs a license key from LibreDB, and Platform does not start in self-hosted mode without one.
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- [ ] Studio users can connect to the platform with limited permissions(studio, account, support/tickets)
- [ ] All libredb-studio database connections support
- [ ] Kubernetes
- [x] Kubernetes (Helm chart `charts/libredb-platform`)
- [ ] GitOps

---
Expand Down
7 changes: 7 additions & 0 deletions charts/libredb-platform/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.git/
*.swp
*.bak
*.tmp
tests/
README.md.gotmpl
6 changes: 6 additions & 0 deletions charts/libredb-platform/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies:
- name: libredb-studio
repository: https://libredb.org/libredb-studio/
version: 0.1.100
digest: sha256:a56f4096de2bd4d02e73a5b984ba8040490565a767be05a123ffb2abb88a92d6
generated: "2026-10-20T02:08:21.66270654+03:00"
31 changes: 31 additions & 0 deletions charts/libredb-platform/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: v2
name: libredb-platform
description: >-
LibreDB Platform, database access governance, with LibreDB Studio, on Kubernetes: Platform, its migrate job,
Studio under governance, and optionally a managed PostgreSQL and Keycloak. Source-available under the
Elastic License 2.0; every install needs a license from LibreDB.
type: application
# The chart version; appVersion is the Platform release whose images and Studio chart it pins
# (installer/release/release.config.json, which installer/test/unit/kubernetes/chart.test.ts compares).
version: 0.1.0
appVersion: "1.0.1"
kubeVersion: ">=1.28.0-0"
home: https://libredb.org/platform/
sources:
- https://github.com/libredb/libredb-platform
license: Elastic-2.0
keywords:
- database
- governance
- audit
- keycloak
- libredb
maintainers:
- name: LibreDB
url: https://libredb.org
dependencies:
- name: libredb-studio
alias: studio
version: 0.1.100
repository: https://libredb.org/libredb-studio/
condition: studio.enabled
86 changes: 86 additions & 0 deletions charts/libredb-platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# libredb-platform Helm chart

LibreDB Platform, database access governance, with LibreDB Studio, on Kubernetes.
The guide is [docs/deploy/install-kubernetes.md](../../docs/deploy/install-kubernetes.md); this file lists the values.
LibreDB Platform is source-available under the Elastic License 2.0, and every install needs a license from LibreDB.

The chart is the installer's Kubernetes mode driven by Helm.
It renders the values as an installation configuration of `mode: kubernetes`, which its jobs validate with the installer's schema and turn into Secrets, certificates, environments, a PostgreSQL provisioning script and a Keycloak realm with the installer's own code, bundled in `files/libredb-jobs.cjs.gz` (`bun run build:helm-jobs` in `installer/` rebuilds it; a unit test fails while it is stale).

## Install

```sh
helm repo add libredb-studio https://libredb.org/libredb-studio/
helm dependency build charts/libredb-platform
helm install libredb charts/libredb-platform -n libredb --create-namespace \
-f libredb-values.yaml --set-file platform.license.value=acme.license \
--wait --wait-for-jobs --timeout 10m
```

## What it deploys

| Object | When |
| --- | --- |
| Job `libredb-bootstrap` (pre-install, pre-upgrade and pre-rollback hook) with its ServiceAccount and Role | always |
| StatefulSet and Service `libredb-postgres`, provisioning Job | `postgresql.mode: managed` |
| Deployment and Services `keycloak` and `keycloak-edge`, Ingress `keycloak` | `keycloak.mode: managed` |
| Job `libredb-keycloak-configure-<hash>` | a managed Keycloak, or `keycloak.external.provisioning: installer` |
| Job `libredb-platform-migrate-<hash>` | always |
| Job `libredb-restart` (post-install, post-upgrade and post-rollback hook) with its ServiceAccount and Role | always |
| Deployment, Service, Ingress and PVC `libredb-platform` | always |
| The Studio chart (pinned by the release manifest) under governance | `studio.enabled` |
| NetworkPolicies `libredb-postgres` and `keycloak` | `networkPolicy.enabled` |

The bootstrap job writes `libredb-secrets` (generated secrets and seal keys, never replaced), `libredb-tls` (internal CA and leaves), `libredb-platform-env`, `libredb-platform-migrate-env`, `libredb-studio-env`, `libredb-keycloak-config`, `libredb-ca` and the objects of the managed PostgreSQL and Keycloak.
They carry no Helm ownership and outlive `helm uninstall`; back up `libredb-secrets` and `libredb-tls`.
When one of them changes, the restart job restarts the workloads that read it, once Helm applied the release.

## Values

| Value | Default | Meaning |
| --- | --- | --- |
| `installation.name` | `libredb` | The installation's name, which names the internal CA |
| `hosts.platform`, `hosts.studio`, `hosts.auth` | | Required: the public hosts, resolvable from browsers and from the cluster's pods |
| `platform.organizationName` | | Required: the name of the installation's one tenant |
| `platform.queryLogRetentionDays` | `null` (90) | Days Platform keeps query events |
| `platform.license.value` | | The license token; pass it with `--set-file` |
| `platform.license.existingSecret`, `platform.license.key` | `""`, `license` | Or a Secret that holds the token |
| `platform.image` | the release's, by digest | Platform's image |
| `platform.resources`, `nodeSelector`, `tolerations`, `affinity`, `podAnnotations`, `podLabels` | | Platform's pod; `nodeSelector` and `tolerations` also place the jobs that run the Platform image, and `nodeSelector` holds `kubernetes.io/arch: amd64` unless it names another arch |
| `platform.persistence.enabled`, `size`, `storageClass`, `accessModes` | `true`, `5Gi` | Support attachments under `/app/uploads` |
| `tls.mode` | `generated` | `generated`: the internal CA issues the ingress certificates; `secrets`: your TLS Secrets |
| `tls.secrets.platform`, `studio`, `auth` | | With `tls.mode: secrets`, the TLS Secret of each host (`auth` only with a managed Keycloak); repeat `studio` as `studio.ingress.tls[0].secretName` |
| `tls.caBundle` | | With `tls.mode: secrets`, the PEM of the CA that issued them (required) |
| `ingress.enabled`, `className`, `annotations` | `true`, `""`, `nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"` | The Ingress of Platform and of a managed Keycloak; the annotation gives ingress-nginx room for the response headers of Platform's sign-in callback, and other controllers ignore it |
| `postgresql.mode` | `managed` | `managed` or `external` |
| `postgresql.managed.image`, `persistence.size`, `persistence.storageClass`, `resources`, `nodeSelector`, `tolerations`, `affinity` | the release's image, `10Gi` | The managed server |
| `postgresql.external.host`, `port`, `caBundle` | `""`, `5432` | The external server, provisioned once by a DBA with Secret `libredb-postgres-dba-script` |
| `keycloak.mode` | `managed` | `managed` or `external` |
| `keycloak.realm` | `libredb` | The realm |
| `keycloak.firstAdmin.enabled`, `username` | `null`, `libredb-admin` | The first admin account; enabled by default with a managed Keycloak |
| `keycloak.userManager.enabled`, `username` | `null`, `libredb-user-manager` | The user manager account; enabled by default with a managed Keycloak |
| `keycloak.managed.image`, `resources`, `nodeSelector`, `tolerations`, `affinity` | the release's image | The managed Keycloak |
| `keycloak.managed.edge.image`, `resources` | the release's Caddy | The installer's edge in front of Keycloak's public paths |
| `keycloak.external.baseUrl`, `adminUrl` | | Your Keycloak; `hosts.auth` must be the host of `baseUrl` |
| `keycloak.external.provisioning` | `installer` | `installer`: the chart's job configures the realm; `manual`: your identity team does |
| `keycloak.external.adminRealm`, `adminSecret`, `adminClientId`, `adminUser` | `master` | With `installer`: the Secret with `admin-client-secret` (client) or `admin-password` (user) |
| `keycloak.external.clientSecretsSecret` | | With `manual`: the Secret with `studio-client-secret` and `platform-client-secret` |
| `keycloak.external.caBundle` | | The PEM of your Keycloak's CA, when a private CA issued its certificate |
| `network.httpsProxy` | | An http(s) proxy for the apps' outbound HTTPS, without credentials |
| `jobs.backoffLimit`, `activeDeadlineSeconds`, `resources` | `6`, `900` | The chart's jobs |
| `networkPolicy.enabled` | `true` | PostgreSQL and Keycloak's Admin API reachable only from this release's pods |
| `platform.sealKeys.issueNew`, `activeKid`, `retire` | `false`, `""`, `[]` | The seal key rotation, one upgrade per step; see the guide's "Rotate secrets and the seal key" |
| `platform.authSecretPrevious.existingSecret`, `key` | `""`, `auth-secret-previous` | During a rotation of Platform's `AUTH_SECRET`, a Secret with the previous value |
| `global.compatibility.openshift.adaptSecurityContext` | `auto` | `auto` leaves the pods' uids to OpenShift's SCC when the cluster is OpenShift, `force` always, `disabled` never; Studio reads it too |
| `imagePullSecrets` | `[]` | For every pod of the chart (Studio has its own under `studio:`) |
| `studio.*` | governance defaults | The Studio chart's values; repeat `hosts.studio` in `studio.ingress` and the issuer in `studio.config.oidcIssuer` |

The chart refuses to render, naming the value, when a required value is missing or two copies of a host or the issuer disagree.

## Tests

- `installer/test/unit/kubernetes/chart.test.ts` renders the chart with `helm template` and holds it to the installer: render guards, the installation document against the schema, the release manifest's pins, Studio's and Keycloak's environments against the installer's renderers, the bootstrap Role against every object the job writes, and the pods' security.
- `tests/e2e/run-e2e.sh` installs on a local kind cluster and runs the browser suite of `installer/e2e/kubernetes`.
- `tests/e2e/run-openshift.sh` does the same on OpenShift, in the namespace of the current `oc login`, under the `restricted-v2` SCC.
- `tests/e2e/run-rotation.sh` rotates the seal key and the generated secrets; `tests/e2e/run-resilience.sh` kills pods and restarts the node; `tests/e2e/run-soak.sh` runs one installation for hours.
- `tests/e2e/run-restore.sh` backs up, deletes the namespace and restores; `tests/e2e/run-offline.sh` installs the packaged archive from an image mirror with no network for Helm.
Binary file not shown.
47 changes: 47 additions & 0 deletions charts/libredb-platform/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
LibreDB Platform {{ .Chart.AppVersion }} is installing in namespace {{ .Release.Namespace }}.

Platform https://{{ .Values.hosts.platform }}
Studio https://{{ .Values.hosts.studio }}
Keycloak {{ include "libredb-platform.issuer" . }}

Watch it come up; Platform turns ready once its migrate job has run:

kubectl -n {{ .Release.Namespace }} get pods,jobs
kubectl -n {{ .Release.Namespace }} logs deploy/libredb-platform | grep -i licen

{{- if eq .Values.tls.mode "generated" }}

The internal CA issued the ingress certificates. Import it into the trust store of every workstation that opens the
three URLs (it can sign a certificate for any name, so import it only where the cluster's operators are trusted):

kubectl -n {{ .Release.Namespace }} get secret libredb-tls-platform -o jsonpath='{.data.ca\.crt}' | base64 -d > libredb-ca.crt
{{- end }}
{{- if include "libredb-platform.managedKeycloak" . }}

First sign-in: open Platform, choose Continue with SSO and sign in as {{ .Values.keycloak.firstAdmin.username }} with
its temporary password; Keycloak then asks for a new password and a TOTP device.

kubectl -n {{ .Release.Namespace }} get secret libredb-keycloak-config -o jsonpath='{.data.first-admin-password}' | base64 -d; echo

{{ .Values.keycloak.userManager.username }} manages the realm's users in Keycloak's console, which is not exposed by the ingress:

kubectl -n {{ .Release.Namespace }} get secret libredb-keycloak-config -o jsonpath='{.data.user-manager-password}' | base64 -d; echo
kubectl -n {{ .Release.Namespace }} port-forward svc/keycloak 18080:8080
# then open http://127.0.0.1:18080/admin/{{ .Values.keycloak.realm }}/console/
{{- end }}
{{- if eq .Values.postgresql.mode "external" }}

The external PostgreSQL server needs its one-time provisioning by a DBA. The script holds SCRAM verifiers only:

kubectl -n {{ .Release.Namespace }} get secret libredb-postgres-dba-script -o jsonpath='{.data.provision\.sql}' | base64 -d > libredb-provision.sql
psql "host={{ .Values.postgresql.external.host }} port={{ .Values.postgresql.external.port }} dbname=postgres user=<admin> sslmode=verify-full sslrootcert=<CA file>" -f libredb-provision.sql

The migrate job retries until the roles exist.
{{- end }}

BACK UP THE GENERATED SECRETS NOW. Secret libredb-secrets holds the seal keys that open every database credential
Platform stores; it outlives `helm uninstall` on purpose, and a lost key cannot be recovered:

kubectl -n {{ .Release.Namespace }} get secret libredb-secrets libredb-tls libredb-installer-state -o yaml > libredb-secrets-backup.yaml

A full backup set adds a dump of each database; "Back up and restore" in docs/deploy/install-kubernetes.md has both.
Loading
Loading