Kubernetes Deployment
Deploy Kreuzberg to Kubernetes with proper OCR configuration, permissions, and health checks.
Helm Chart v4.8.4
Section titled “Helm Chart v4.8.4”The recommended way to deploy Kreuzberg on Kubernetes is via the official Helm chart, published as an OCI artifact to GitHub Container Registry.
Install
Section titled “Install”helm install kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg --version 4.8.4Configure
Section titled “Configure”Override defaults with a values.yaml file:
replicaCount: 3
image: tag: "4.8.4"
kreuzberg: logLevel: "info" ocrLanguage: "eng"
resources: requests: memory: "1Gi" cpu: "1000m" limits: memory: "4Gi" cpu: "2000m"
cache: enabled: true size: 5Gi
ingress: enabled: true className: "nginx" hosts: - host: kreuzberg.example.com paths: - path: / pathType: Prefix tls: - secretName: kreuzberg-tls hosts: - kreuzberg.example.com
autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80
podDisruptionBudget: enabled: true minAvailable: 1helm install kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg \ --version 4.8.4 \ -f values.yamlUpgrade
Section titled “Upgrade”helm upgrade kreuzberg oci://ghcr.io/kreuzberg-dev/charts/kreuzberg --version 4.8.4What’s Included
Section titled “What’s Included”The chart creates the following resources:
| Resource | Description | Conditional |
|---|---|---|
| Deployment | Main application with health probes and security hardening | Always |
| Service | ClusterIP service on port 80 → 8000 | Always |
| ServiceAccount | Dedicated service account | Always |
| PersistentVolumeClaim | Cache for embedding models and assets | cache.enabled |
| Ingress | HTTP(S) ingress with TLS | ingress.enabled |
| HorizontalPodAutoscaler | CPU/memory-based autoscaling | autoscaling.enabled |
| PodDisruptionBudget | Availability during disruptions | podDisruptionBudget.enabled |
All values are documented in the chart’s values.yaml.
If you need finer control over the manifests, see the raw YAML examples below.
Quick Start
Section titled “Quick Start”apiVersion: apps/v1kind: Deploymentmetadata: name: kreuzberg-apispec: replicas: 2 selector: matchLabels: app: kreuzberg template: metadata: labels: app: kreuzberg spec: containers: - name: kreuzberg image: ghcr.io/kreuzberg-dev/kreuzberg-full:latest ports: - containerPort: 8000 name: http env: - name: RUST_LOG value: "info" - name: TESSDATA_PREFIX value: "/usr/share/tesseract-ocr/5/tessdata" resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 10---apiVersion: v1kind: Servicemetadata: name: kreuzberg-apispec: selector: app: kreuzberg ports: - protocol: TCP port: 80 targetPort: 8000 type: LoadBalancerkubectl apply -f minimal-deployment.yamlTesseract Configuration
Section titled “Tesseract Configuration”TESSDATA_PREFIX (Critical)
Section titled “TESSDATA_PREFIX (Critical)”Without TESSDATA_PREFIX, OCR silently falls back to non-OCR extraction. Official images ship Tesseract 5.x with tessdata at /usr/share/tesseract-ocr/5/tessdata/.
env:- name: TESSDATA_PREFIX value: "/usr/share/tesseract-ocr/5/tessdata"- name: KREUZBERG_OCR_LANGUAGE value: "eng"- name: KREUZBERG_CACHE_DIR value: "/app/.kreuzberg"- name: HF_HOME value: "/app/.kreuzberg/huggingface"Pre-installed languages: eng, spa, fra, deu, ita, por, chi_sim, chi_tra, jpn, ara, rus, hin
Custom Languages via ConfigMap
Section titled “Custom Languages via ConfigMap”kubectl create configmap tessdata \ --from-file=/path/to/eng.traineddata \ --from-file=/path/to/deu.traineddataspec: containers: - name: kreuzberg env: - name: TESSDATA_PREFIX value: "/etc/tessdata" volumeMounts: - name: tessdata mountPath: /etc/tessdata volumes: - name: tessdata configMap: name: tessdataFor large custom language sets, use a PVC instead of a ConfigMap.
Verify Tesseract
Section titled “Verify Tesseract”kubectl exec -it deployment/kreuzberg-api -- tesseract --versionkubectl exec -it deployment/kreuzberg-api -- tesseract --list-langskubectl exec -it deployment/kreuzberg-api -- printenv TESSDATA_PREFIXPermissions
Section titled “Permissions”Kreuzberg runs as non-root (UID 1000, GID 1000). Fix PVC permissions with either approach:
spec: initContainers: - name: init-permissions image: busybox:1.37-glibc command: ['sh', '-c', 'chown -R 1000:1000 /app/.kreuzberg'] securityContext: runAsUser: 0 volumeMounts: - name: cache mountPath: /app/.kreuzberg containers: - name: kreuzberg volumeMounts: - name: cache mountPath: /app/.kreuzbergspec: securityContext: fsGroup: 1000 containers: - name: kreuzberg securityContext: runAsUser: 1000 runAsGroup: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]Health Checks
Section titled “Health Checks”containers:- name: kreuzberg livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 2 startupProbe: httpGet: path: /health port: 8000 periodSeconds: 10 failureThreshold: 30Logging
Section titled “Logging”env:- name: RUST_LOG value: "kreuzberg=debug,warn"Levels: trace, debug, info, warn, error
kubectl logs deployment/kreuzberg-api --tail=50kubectl logs deployment/kreuzberg-api -fkubectl logs deployment/kreuzberg-api --previousProduction Deployment
Section titled “Production Deployment”Full production manifest with namespace, PVC, security context, init container, PDB, and all probes:
apiVersion: v1kind: Namespacemetadata: name: kreuzberg---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: kreuzberg-cache namespace: kreuzbergspec: accessModes: [ReadWriteOnce] resources: requests: storage: 2Gi---apiVersion: apps/v1kind: Deploymentmetadata: name: kreuzberg-api namespace: kreuzbergspec: replicas: 3 selector: matchLabels: app: kreuzberg template: metadata: labels: app: kreuzberg spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault initContainers: - name: init-cache image: busybox:1.37-glibc command: ['sh', '-c', 'mkdir -p /app/.kreuzberg && chown -R 1000:1000 /app/.kreuzberg'] securityContext: runAsUser: 0 volumeMounts: - name: cache mountPath: /app/.kreuzberg containers: - name: kreuzberg image: ghcr.io/kreuzberg-dev/kreuzberg-full:latest ports: - containerPort: 8000 name: http env: - name: RUST_LOG value: "info" - name: TESSDATA_PREFIX value: "/usr/share/tesseract-ocr/5/tessdata" - name: KREUZBERG_CACHE_DIR value: "/app/.kreuzberg" - name: HF_HOME value: "/app/.kreuzberg/huggingface" - name: KREUZBERG_CORS_ORIGINS value: "https://app.example.com" - name: KREUZBERG_MAX_UPLOAD_SIZE_MB value: "500" args: ["serve", "--host", "0.0.0.0", "--port", "8000"] resources: requests: memory: "1Gi" cpu: "1000m" limits: memory: "4Gi" cpu: "2000m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 15 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 10 startupProbe: httpGet: path: /health port: 8000 periodSeconds: 10 failureThreshold: 30 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] volumeMounts: - name: cache mountPath: /app/.kreuzberg - name: tmp mountPath: /tmp volumes: - name: cache persistentVolumeClaim: claimName: kreuzberg-cache - name: tmp emptyDir: {}---apiVersion: v1kind: Servicemetadata: name: kreuzberg-api namespace: kreuzbergspec: type: LoadBalancer selector: app: kreuzberg ports: - protocol: TCP port: 80 targetPort: 8000---apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: kreuzberg-pdb namespace: kreuzbergspec: minAvailable: 1 selector: matchLabels: app: kreuzbergkubectl apply -f production-deployment.yamlHigh Availability
Section titled “High Availability”For HA deployments, add pod anti-affinity, rolling update strategy, and a ConfigMap for extraction settings:
spec: replicas: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: [kreuzberg] topologyKey: kubernetes.io/hostnameTroubleshooting
Section titled “Troubleshooting”Diagnostic Commands
Section titled “Diagnostic Commands”kubectl logs deployment/kreuzberg-api --tail=200kubectl describe deployment kreuzberg-apikubectl get events -n kreuzbergkubectl exec -it deployment/kreuzberg-api -- env | sortkubectl port-forward service/kreuzberg-api 8000:8000 && curl http://localhost:8000/healthNext Steps
Section titled “Next Steps”- Docker Deployment — container configuration and image variants
- API Server Guide — endpoint documentation
- OCR Guide — backend installation and language setup
- Configuration — all configuration options