LibreChat on Kubernetes without Helm, with Kapitan
LibreChat v0.8.8, a chat UI for many model providers, on the MongoDB of the included recipe mongodb, with registration closed and without search, rendered by the kadet-hub kubernetes generator 1.14.0 from LibreChat’s Helm chart librechat 3.0.0.
- Latest version:
1.0.0, published - Source chart:
oci://ghcr.io/librechat-ai/librechat-chart/librechat:3.0.0@sha256:5d50ecaf2685295fb5d28da016a964b03610e7897d6e366c0b573086fb23775a - Proof level:
deploys, compared with the chart by the kadet-hub/recipes CI renderspassed in the release run of with check 0.18.0, kind v0.33.0- Output capabilities: none
- Inventory backends:
omegaconf - Generators: kubernetes 1.14.0
sha256:787129e1eaed6b9bc868fe4c4aec58d40de6a89fc3cc08424a4cb5822b855080 - Image security: grade F 146 findings, one per advisory and package, in the 1 image of version 1.0.0: CRITICAL 5, HIGH 54, MEDIUM 78, LOW 9, UNKNOWN 0. Scanned 2026-11-07 11:47 UTC. Full report
The grade rates the upstream images the recipe pins, not the recipe:registry.librechat.ai/librechat-ai/librechat.
What it deploys
recipes.librechat(init.yml)recipes.librechat.librechat(librechat.yml):librechat-librechat
Required parameters
applications.librechat.creds_key is CREDS_KEY, 64 hexadecimal digits
such as the output of openssl rand -hex 32, and
applications.librechat.creds_iv is CREDS_IV, 32 hexadecimal digits
(openssl rand -hex 16). LibreChat encrypts stored credentials with them,
such as API keys that users enter themselves, so keep both for the life of
the database. applications.librechat.jwt_secret and
applications.librechat.jwt_refresh_secret are JWT_SECRET and
JWT_REFRESH_SECRET, which sign the logins. A new value logs every user
out. applications.mongodb.password is the password of MongoDB’s user
root, with which LibreChat connects. It goes into MONGO_URI unencoded,
so use letters and digits only. Set all of them as refs with the backend
of your choice. Without one of them the compile stops with
Interpolation key '<key>' not found.
Include
A target includes the recipe and its options as classes and can override anything the recipe sets:
classes:
- recipes.librechat
parameters:
namespace: librechat
kapitan:
vars:
target: librechat
applications:
librechat:
creds_key: <ref>
creds_iv: <ref>
jwt_secret: <ref>
jwt_refresh_secret: <ref>
mongodb:
password: <ref>
Fetching it
The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.
A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:
parameters:
kapitan:
dependencies:
- type: oci
source: ghcr.io/kadet-hub/recipes/librechat:1.0.0@sha256:90fe2ead5bc1e9efe07a704a8dbb36f255f32313376284ea4a25875f782f3dee
output_path: inventory/classes/recipes/librechat
Version 1.0.0 includes mongodb 1.1.0. The consumer workflow adds the included recipes at the digests the index lists. Kapitan's --fetch reads no index, so for a local compile pull them by digest first:
tmp=$(mktemp -d)
oras pull -o "$tmp" ghcr.io/kadet-hub/recipes/mongodb@sha256:bed34997d85a978e3ce5ebfd2f87284a69c38a29bb64c80e81c9b3d701eea525
mkdir -p inventory/classes/recipes/mongodb
tar -xf "$tmp/mongodb.tar" -C inventory/classes/recipes/mongodb --no-same-owner
The first command fetches the recipe into inventory/classes/recipes/librechat, the second the generators and compiles:
kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch
The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe and of the recipes it includes:
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/librechat@sha256:90fe2ead5bc1e9efe07a704a8dbb36f255f32313376284ea4a25875f782f3dee \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/mongodb@sha256:bed34997d85a978e3ce5ebfd2f87284a69c38a29bb64c80e81c9b3d701eea525 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.
Settings
| Chart value | Inventory |
|---|---|
image.tag |
components.librechat-librechat.image, tag and digest |
librechat.configEnv |
generators.kubernetes.configs.librechat-librechat-configenv.data.<variable>.value |
global.librechat.env |
components.librechat-librechat.env.<variable> |
librechat.imageVolume.size, 10G |
components.librechat-librechat.persistent_volume_claims.librechat-librechat-images.storage |
resources |
components.librechat-librechat.resources |
ingress |
a route or Ingress of the target to the Service librechat-librechat on port 3080, with DOMAIN_CLIENT and DOMAIN_SERVER set to its URL |
| the MongoDB server, its size and version | applications.mongodb and the settings mongodb’s README lists |
The pod annotation checksum/configEnv is the chart’s hash of its
ConfigMap for tests/values.yaml. It does not follow a change of the
ConfigMap, so restart the Deployment after one.
The claim librechat-librechat-images keeps the images below
/app/client/public/images, as the chart’s imageVolume does. Other
uploads and the logs stay in the container. LibreChat’s schedules need
Redis, which the recipe does not render, or SCHEDULES_SINGLE_PROCESS: "true" for a single replica. Without either LibreChat refuses new
schedules and logs an error at every start.
The chart’s subcharts are not rendered. MongoDB is the included recipe
mongodb in place of Bitnami’s chart. Meilisearch, which LibreChat’s
conversation search needs, the RAG API with its PostgreSQL for file
search, and Redis are left out. LibreChat’s search stays off while
SEARCH is unset. A target that runs Meilisearch elsewhere sets SEARCH,
MEILI_HOST and MEILI_MASTER_KEY, the RAG API RAG_API_URL, and Redis
USE_REDIS and REDIS_URI, as described above. The chart’s Ingress, the
Langfuse fanout and the HorizontalPodAutoscaler are off. The chart’s test
pod Pod/librechat-librechat-test-connection, a Helm hook, is left out.
Proof
check librechat --level deploys with tests/values.yaml and
tests/target.yml, on kind for each Kubernetes minor CI tests, with check
0.16.0. The objects of mongodb are applied with the recipe and left out
of the comparison. The remaining differences are of the classes Selector
scheme, Explicit default (the pod’s automountServiceAccountToken: true)
and Referenced Secret (the Secret librechat-credentials-env).
tests/values.yaml sets the chart’s lifecycle to null, because the
chart renders its default {} as an empty lifecycle on the container,
which the generator does not render. Both mean no hook.
tests/smoke requires that the pods pass Pod Security restricted. It
starts an OpenAI-compatible stub, busybox’s httpd with a script that
lists the model stub and answers every completion with the same reply,
streamed or not, which tests/target.yml configures as the endpoint
openAI. It creates the first user with npm run create-user as above.
From a pod of the LibreChat image, through the Service
librechat-librechat on port 3080, it
- gets
OKfrom/health - gets 403 for a registration
- logs in as the user and requires the role
ADMIN - requires the model
stubof the endpointopenAIin/api/models - sends a message to
stuband waits until the conversation holds the stub’s reply - deletes the LibreChat and MongoDB pods and, once their successors are ready, logs in again and reads the conversation and both messages back.
Not tested: the web UI, a real model provider, librechat.yaml, file
uploads, search, and replacing a running Helm release.
Versions
1.0.0, :sha256:90fe2ead5bc1e9efe07a704a8dbb36f255f32313376284ea4a25875f782f3dee
The recipe's files are in kadet-hub/recipes. All recipes