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
51 changes: 41 additions & 10 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ processes writing one index would corrupt it. The chart is built around that fac
- **Hardened by default**: non-root (uid 10001), read-only root filesystem, dropped
capabilities, `RuntimeDefault` seccomp, and the service-account token is not mounted.

The **server** is the primary, recommended surface. The **UI** is optional and, because it
bundles the engine and writes its own index, runs as an *independent* instance with its
own volume when enabled (build its index with the in-app **Reindex** button).
The **server** is the primary, recommended surface. The **UI** is optional and runs in one
of two topologies:

- **Shared (recommended for a read-only/demo UI):** `ui.useServerIndex=true` mounts the
server's index volume **read-only**, so the UI serves exactly what the index/reindex
Jobs built — no separate volume, always in sync, and it can never corrupt the writer's
store. Reindexing stays a server-side Job.
- **Independent (default):** the UI gets its **own** data volume and bundles the engine.
Nothing populates that volume automatically (the index Jobs drive the *server's* volume),
so you build it with the in-app **Reindex** button — which is **disabled in demo mode**,
so a demo UI left on the default shows an empty index (0 files / 0 chunks).

---

Expand Down Expand Up @@ -142,7 +150,9 @@ Full list with comments: [`values.yaml`](helm/coderag/values.yaml). The most-use
| `server.service.type` | `ClusterIP` | `ClusterIP` · `NodePort` · `LoadBalancer`. |
| `index.initJob.enabled` | `true` | Build the index automatically on install/upgrade. |
| `index.cronjob.enabled` | `false` | Recurring reindex (`index.cronjob.schedule`). |
| `ui.enabled` | `false` | Also deploy the web UI (independent instance). |
| `ui.enabled` | `false` | Also deploy the web UI. |
| `ui.useServerIndex` | `false` | UI serves the **server's** index (read-only) instead of its own empty volume. |
| `ui.coLocateWithServer` | `false` | Pin the UI onto the server's node — required with `useServerIndex` on RWO storage. |
| `ingress.enabled` | `false` | Expose via an Ingress (**add TLS + auth** — the API has none). |
| `resources` (`server.*`, `ui.*`) | see values | CPU/memory requests & limits. |

Expand Down Expand Up @@ -406,13 +416,27 @@ ingress:

### Also run the web UI

**Recommended — UI serves the server's index (read-only):**

```bash
--set ui.enabled=true
--set ui.enabled=true \
--set ui.useServerIndex=true
# On ReadWriteOnce storage (the default), also pin the UI onto the server's node:
--set ui.coLocateWithServer=true
# Omit coLocateWithServer if persistence uses a ReadWriteMany storageClass.
```

The UI gets its own data volume and clones the same repo. Open it via port-forward
(`svc/coderag-ui:8501`) or add an Ingress path with `service: ui`, then click **Reindex**
in the sidebar to build its index.
The UI mounts the server's index volume read-only, so it shows whatever the
init/reindex Jobs built — nothing to reindex from the UI, and it stays in sync with the
server. This is the right choice for a public/demo UI, where the in-app Reindex button is
disabled. Open it via port-forward (`svc/coderag-ui:8501`) or an Ingress path with
`service: ui`.

> **Independent UI (default, `ui.useServerIndex=false`):** the UI gets its **own** data
> volume and clones the same repo, but **nothing populates that volume** — you must click
> **Reindex** in the sidebar to build it (impossible in demo mode). If your UI shows
> 0 files / 0 chunks, this is almost always why: the index Jobs filled the *server's*
> volume, not the UI's. Switch to `ui.useServerIndex=true`.

### Pin to an immutable image (reproducible / air-gapped)

Expand Down Expand Up @@ -489,12 +513,19 @@ helm template coderag deploy/helm/coderag -f deploy/helm/coderag/ci/full-values.
`/data`, and `/home/coderag`. If a backend insists on another path, mount it via
`extraVolumes`/`extraVolumeMounts`, or relax the hardening:
`--set securityContext.readOnlyRootFilesystem=false`.
- **UI shows 0 files / 0 chunks / 0 vectors** — the UI is on its own (empty) data volume
while the index Jobs populated the *server's* volume. They are different PVCs
(`…-ui-data` vs `…-server-data`). Set `ui.useServerIndex=true` so the UI serves the
server's index read-only (add `ui.coLocateWithServer=true` on `ReadWriteOnce` storage).
The independent UI only fills its own volume via the in-app **Reindex** button, which is
disabled in demo mode.

## Limitations

- **Single writer by design** — do not raise `replicas`. For higher search throughput,
put a cache/load balancer in front of the read endpoints; the index itself stays
single-writer.
- **`ReadWriteOnce`** ties the index to one node at a time; that's expected for the embedded store.
- The **UI**, when enabled, maintains a *separate* index from the server. For a single
shared index, run the server and point browsers/tools at its REST API.
- The **UI**, when enabled, defaults to a *separate* index from the server. For a single
shared index, set `ui.useServerIndex=true` (the UI reads the server's volume read-only),
or run the server alone and point browsers/tools at its REST API.
4 changes: 4 additions & 0 deletions deploy/helm/coderag/ci/full-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ workspace:

ui:
enabled: true
# Exercise the shared-index topology: UI mounts the server's volume read-only (no
# …-ui-data PVC), with node co-location for the ReadWriteOnce volume.
useServerIndex: true
coLocateWithServer: true

secrets:
create: true
Expand Down
15 changes: 15 additions & 0 deletions deploy/helm/coderag/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ Service name / in-cluster base URL for the HTTP API (used by the index jobs).
{{- printf "http://%s:%v" (include "coderag.serverServiceName" .) .Values.server.service.port -}}
{{- end -}}

{{/*
Name of the PVC holding the SERVER's index — the chart-managed claim, or the
existingClaim the operator supplied. Used by the server pod and by the UI when
ui.useServerIndex shares that same volume.
*/}}
{{- define "coderag.serverDataClaimName" -}}
{{- if .Values.persistence.existingClaim -}}
{{- .Values.persistence.existingClaim -}}
{{- else -}}
{{- printf "%s-server-data" (include "coderag.fullname" .) -}}
{{- end -}}
{{- end -}}

{{/*
envFrom block shared by the server and UI containers: non-secret config plus the
optional API-key Secret.
Expand Down Expand Up @@ -242,6 +255,8 @@ Pod volumes for a writer (server/ui). Call with (dict "ctx" . "component" "serve
persistentVolumeClaim:
{{- if and (eq $component "server") $ctx.Values.persistence.existingClaim }}
claimName: {{ $ctx.Values.persistence.existingClaim }}
{{- else if and (eq $component "ui") $ctx.Values.ui.useServerIndex }}
claimName: {{ include "coderag.serverDataClaimName" $ctx }}
{{- else if and (eq $component "ui") $ctx.Values.ui.persistence.existingClaim }}
claimName: {{ $ctx.Values.ui.persistence.existingClaim }}
{{- else }}
Expand Down
37 changes: 37 additions & 0 deletions deploy/helm/coderag/templates/ui-deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{{- if .Values.ui.enabled -}}
{{- if .Values.ui.useServerIndex }}
{{- if not .Values.server.enabled }}
{{- fail "ui.useServerIndex=true requires server.enabled=true (the UI shares the server's index volume)." }}
{{- end }}
{{- if not .Values.persistence.enabled }}
{{- fail "ui.useServerIndex=true requires persistence.enabled=true (there is a server PVC to share; an emptyDir cannot be shared across pods)." }}
{{- end }}
{{- end }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -60,6 +68,13 @@ spec:
value: {{ .Values.ui.containerPort | quote }}
- name: HOME
value: /home/coderag
{{- if .Values.ui.useServerIndex }}
# The shared server index is mounted read-only, so the embedding model can't
# be cached onto it. Point the cache at the writable `home` volume instead
# (overrides CODERAG_CACHE_DIR from the ConfigMap) so query embedding works.
- name: CODERAG_CACHE_DIR
value: /home/coderag/.model-cache
{{- end }}
{{- with .Values.ui.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
Expand Down Expand Up @@ -90,6 +105,11 @@ spec:
volumeMounts:
- name: data
mountPath: {{ .Values.persistence.mountPath }}
{{- if .Values.ui.useServerIndex }}
# Shared server index: read-only so the UI can never corrupt the single
# writer's LanceDB store (reindex stays a server-side Job).
readOnly: true
{{- end }}
- name: workspace
mountPath: {{ .Values.workspace.mountPath }}
readOnly: {{ .Values.workspace.readOnly }}
Expand All @@ -109,10 +129,27 @@ spec:
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if and .Values.ui.useServerIndex .Values.ui.coLocateWithServer }}
# ReadWriteOnce volume shared with the server attaches to a single node, so pin the
# UI pod onto the node already running the server pod. (Set coLocateWithServer=false
# if persistence uses a ReadWriteMany class — then any node can mount the volume.)
affinity:
{{- with .Values.affinity }}
{{- toYaml . | nindent 8 }}
{{- end }}
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
{{- include "coderag.selectorLabels" . | nindent 18 }}
app.kubernetes.io/component: server
topologyKey: kubernetes.io/hostname
{{- else }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
Expand Down
2 changes: 1 addition & 1 deletion deploy/helm/coderag/templates/ui-pvc.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- if and .Values.ui.enabled .Values.persistence.enabled (not .Values.ui.persistence.existingClaim) -}}
{{- if and .Values.ui.enabled .Values.persistence.enabled (not .Values.ui.useServerIndex) (not .Values.ui.persistence.existingClaim) -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
Expand Down
32 changes: 28 additions & 4 deletions deploy/helm/coderag/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,37 @@ server:
# -- Extra env vars (list of {name,value|valueFrom}) for the server container.
extraEnv: []

# --- Web UI (optional; runs as an INDEPENDENT instance with its own index) ---
# The UI bundles the engine and writes its own index, so when enabled it gets a
# separate data volume and workspace. Build its index with the in-app "Reindex"
# button. For a shared, server-maintained index, prefer the server + an ingress.
# --- Web UI (optional) ---
# The UI image bundles the engine and reads a LanceDB store at CODERAG_STORE_DIR.
# Two topologies:
#
# useServerIndex: false (default) — INDEPENDENT instance with its OWN data volume
# (…-ui-data). Nothing populates it automatically: the index/reindex Jobs drive the
# SERVER's volume, not this one. Build it with the in-app "Reindex" button — which is
# DISABLED in demo mode, so a demo UI left on the default will show 0 files/0 chunks.
#
# useServerIndex: true (RECOMMENDED for a read-only / demo UI) — the UI mounts the
# SERVER's index volume READ-ONLY and serves whatever the index Job built. No second
# writer, no separate PVC, always in sync with the server. See `useServerIndex` below.
ui:
enabled: false
containerPort: 8501
# -- Share the SERVER's index instead of keeping a separate (empty) UI volume. When
# true the UI mounts the server's data PVC READ-ONLY at persistence.mountPath, so it
# shows the index built by the init/reindex Jobs and can never corrupt the writer's
# store. No …-ui-data PVC is created. Requirements & caveats:
# * The server (persistence) must be enabled — that's the volume being shared.
# * Access mode: the server PVC is ReadWriteOnce by default, so the UI and server
# pods must land on the SAME node. Either set `coLocateWithServer: true` below, or
# give persistence a ReadWriteMany storageClass (NFS/CephFS/EFS/Longhorn-RWX/…).
# * The UI's model cache is redirected to a writable in-pod volume automatically
# (the shared index mount is read-only), so query embedding still works.
# * Reindex stays a SERVER action (Job/CronJob); the in-app button is irrelevant here.
useServerIndex: false
# -- Pin the UI pod onto the same node as the server pod via podAffinity. REQUIRED with
# useServerIndex on ReadWriteOnce storage (a single RWO volume attaches to one node).
# Harmless (but unnecessary) on ReadWriteMany. Ignored when useServerIndex is false.
coLocateWithServer: false
# -- Deployment update strategy. Defaults to Recreate: the UI is a single writer on
# a ReadWriteOnce volume, so the old pod must release the claim before the new one
# binds it. The cost is a brief gap with no Ready pod on every image change — visible
Expand Down
Loading