Kubernetes
Deploying with the Helm chart, the supported path for production self-hosting.
The chart lives at helm/codeapi and deploys the whole stack: API, worker +
sandbox pods, file server, tool call server, egress gateway, the package-init
job, network policies, and optionally Redis and MinIO.
Prerequisites
- Kubernetes 1.24+, Helm 3.x,
kubectl - Nodes with
/dev/kvmfor microVM mode (or see running without KVM) - A
StorageClassfor the packages PVC
Signing keys are required
The chart ships no default keypair
executionManifest.privateKey and executionManifest.publicKey are empty in
values.yaml, and helm install / helm upgrade fails fast when
workerSandbox.enabled=true (the default) and either is unset. This is
deliberate: a shipped default keypair would be a publicly known signing key.
The worker signs every sandbox execute request with an Ed25519 private key. The sandbox runner receives only the public verifier, so compromising a runner does not give an attacker the ability to mint new manifests.
# Generate the Ed25519 private key (PEM)
openssl genpkey -algorithm ed25519 -out manifest-signing.pem
# Extract the public key (PEM)
openssl pkey -in manifest-signing.pem -pubout -out manifest-signing.pub.pem
# base64-encoded DER values for the chart
PRIVATE_KEY=$(openssl pkey -in manifest-signing.pem -outform DER | base64)
PUBLIC_KEY=$(openssl pkey -in manifest-signing.pem -pubout -outform DER | base64)
helm install codeapi ./helm/codeapi \
--set executionManifest.privateKey="$PRIVATE_KEY" \
--set executionManifest.publicKey="$PUBLIC_KEY"For production, prefer external secrets management (Vault, AWS Secrets Manager,
Sealed Secrets) or deploy-time --set over committing key material to a values
file.
values-local.yaml carries a test-only keypair for minikube development.
It is publicly known: the same keypair is hardcoded in the unit tests. Never
use it outside local development.
Local development on minikube
Start the cluster
minikube start --cpus=4 --memory=8192Build the images inside minikube
eval $(minikube docker-env)
docker build -t codeapi-api:latest -f service/Dockerfile.api .
docker build -t codeapi-worker:latest -f service/Dockerfile.worker .
docker build -t codeapi-sandbox-runner:latest -f api/Dockerfile .
docker build -t codeapi-file-server:latest -f service/Dockerfile --target production .
docker build -t codeapi-tool-call-server:latest -f service/Dockerfile.tool-call-server --target production .
docker build -t codeapi-package-init:latest -f docker/Dockerfile.package-init .Deploy
cd helm/codeapi
helm dependency update # pulls the Redis subchart
helm install codeapi . -f values-local.yamlReach the API
kubectl port-forward svc/codeapi-api 3112:3112
curl http://localhost:3112/v1/healthLanguage packages
The chart includes a package-init Job that runs as a Helm pre-install
hook. It installs the language runtimes into the packages PVC before worker
pods start, so this happens automatically on helm install.
# Watch it
kubectl get jobs -l app.kubernetes.io/component=package-init
kubectl logs job/codeapi-package-init
# Force a rebuild
helm upgrade codeapi ./helm/codeapi \
--set workerSandbox.packages.initJob.forceRebuild=trueTo populate the PVC from a prebuilt directory instead of downloading:
kubectl run pvc-populator --image=alpine --command -- sleep 3600 \
--overrides='{"spec":{"containers":[{"name":"pvc-populator","image":"alpine","command":["sleep","3600"],"volumeMounts":[{"name":"packages","mountPath":"/packages"}]}],"volumes":[{"name":"packages","persistentVolumeClaim":{"claimName":"codeapi-packages"}}]}}'
kubectl wait --for=condition=ready pod/pvc-populator --timeout=60s
kubectl cp ./data/pkgs/. pvc-populator:/packages/
kubectl delete pod pvc-populator
kubectl rollout restart deployment/codeapi-sandbox-runnerKey values
Sandbox isolation
workerSandbox:
replicaCount: 3
# libkrun microVM with its own kernel + NsJail inside. Needs /dev/kvm on the
# node. No privileged mode and no Linux capabilities are required.
kvmEnabled: true
launcher:
vcpus: 2
ramMib: 2048
# Non-privileged KVM access via a device plugin you deploy separately.
kvmDevicePlugin:
enabled: false
resourceName: "" # e.g. devices.kubevirt.io/kvm
# Defense in depth on top of NsJail's inner Kafel policy. Requires the
# profile pre-installed on every node at
# /var/lib/kubelet/seccomp/profiles/nsjail.json (see seccomp/README.md).
seccomp:
enabled: falseWhen seccomp.enabled is false, KVM-mode pods fall back to RuntimeDefault
(still better than nothing) and non-KVM pods use Unconfined, which NsJail's
pivot_root requires. Turning the profile on is a meaningful hardening step
and worth the node-level install.
Use workerSandbox.sandboxRunner scheduling overrides to place only the
untrusted-code tier on KVM-capable nodes:
workerSandbox:
sandboxRunner:
nodeSelector:
codeapi.io/kvm: "true"
tolerations:
- key: codeapi.io/untrusted
operator: Exists
effect: NoScheduleAuthentication
Outside local mode the API verifies JWTs. Configure the verifier through environment variables on the api component:
api:
extraEnv:
- name: CODEAPI_AUTH_PROVIDER
value: librechat-jwt
- name: CODEAPI_JWT_PUBLIC_KEY
valueFrom:
secretKeyRef:
name: codeapi-jwt-verifier
key: public-key
- name: CODEAPI_JWT_KID
value: my-key-idCODEAPI_JWT_PUBLIC_KEYS_DIR (a mounted directory of PEM files named by kid)
and CODEAPI_JWT_JWKS_JSON (inline JWKS) are also supported for key rotation.
See Authentication.
Autoscaling
api:
replicaCount: 2
autoscaling:
enabled: true
workerSandbox:
replicaCount: 3The chart templates HPAs for the API and worker tiers. Queue-depth autoscaling
(KEDA-style) is intentionally left to your platform: scale
workerSandbox.replicaCount on Redis queue depth if you need it.
Network policies
networkPolicy:
enabled: true
otel:
enabled: true
port: 4318
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: observabilityEnabled by default. These are a meaningful part of the security posture: they are what keeps the sandbox tier from reaching anything except the egress gateway. Leave them on.
TLS to Redis
Set REDIS_TLS=true via extraEnv on each component when your Redis requires
TLS.
Operations
# Status
kubectl get pods
kubectl logs deployment/codeapi-api
kubectl logs deployment/codeapi-service-worker
kubectl logs deployment/codeapi-sandbox-runner
# Scale the execution tier
kubectl scale deployment/codeapi-sandbox-runner --replicas=10
# Roll out new images after a rebuild
kubectl rollout restart deployment/codeapi-service-worker
kubectl rollout restart deployment/codeapi-sandbox-runner
# Remove everything
helm uninstall codeapiWhat the chart leaves to you
Deliberately not templated, because they belong to your platform:
- External ingress or service mesh (
api.ingresscovers the simple case) - Queue-depth autoscaling
- Cloud-IAM secret delivery: the
extraEnvhooks on every component cover it