kadet-hub logokadet-hub

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.

What it deploys

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

Not tested: the web UI, a real model provider, librechat.yaml, file uploads, search, and replacing a running Helm release.

Versions

The recipe's files are in kadet-hub/recipes. All recipes