Decision. One chat UI for every model
Alternative. Per-application UIs
Why. Prompts, history, and RAG configuration stay in one place instead of scattering across tools.
ChatGPT-style interface over the local models, with per-user history and presets
Open WebUI is the single chat interface over the model server: prompt libraries, model switching, and retrieval against Qdrant all live here.
Excerpt from argocd-apps/values.yaml in the argocd-apps chart,
with annotations added for this site:
open-webui:
application: true
project: cluster-services
# false
autoSync: false
The full open-webui/values.yaml from the service's
own chart:
image: registry.opi5cluster.co.uk/open-webui:v0.11.0
port: 8080
url: open-webui.opi5cluster.co.uk
## Data Volume
volume:
storageClass: longhorn-ssd-small
size: 10Gi
accessModes: ReadWriteOnce
resources:
requests:
cpu: 1
memory: 2Gi
qdrantHost: qdrant-service.qdrant.svc.cluster.local:6333
openvinoUrl: http://openvino-service.openvino.svc.cluster.local:8000/v3
database:
type: postgresql
name: open_webui templates/data-pvc.yaml Longhorn-backed PersistentVolumeClaim for persistent data.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Namespace }}-data-pvc
namespace: {{ .Release.Namespace }}
spec:
accessModes:
- {{ .Values.volume.accessModes }}
resources:
requests:
storage: {{ .Values.volume.size }}
storageClassName: {{ .Values.volume.storageClass }} templates/http-route.yaml Gateway API HTTPRoute exposing the service through the Istio gateway under opi5cluster.co.uk.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Release.Namespace }}-httproute
namespace: {{ .Release.Namespace }}
annotations:
link.argocd.argoproj.io/external-link: "https://{{ .Values.url }}"
spec:
parentRefs:
- name: istio-gateway
namespace: istio
sectionName: websecure
hostnames:
- {{ .Values.url }}
rules:
- backendRefs:
- name: {{ .Release.Namespace }}-service
port: {{ .Values.port }} templates/rollout.yaml Argo Rollout workload: container spec, probes, resources and rollout strategy.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: {{ .Release.Namespace }}-rollout
namespace: {{ .Release.Namespace }}
labels:
app: {{ .Release.Namespace }}
spec:
replicas: 1
strategy:
canary:
steps:
- setWeight: 25
- pause: {duration: 30s}
- setWeight: 50
- pause: {duration: 45s}
- setWeight: 100
selector:
matchLabels:
app: {{ .Release.Namespace }}
template:
metadata:
labels:
app: {{ .Release.Namespace }}
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: {{ .Release.Namespace }}
image: {{ .Values.image }}
env:
- name: OPENAI_API_BASE_URL
value: {{ .Values.openvinoUrl | quote }}
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: openvino_api_key
- name: WEBUI_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: open_webui_secret_key
- name: VECTOR_DB
value: qdrant
- name: QDRANT_URI
value: {{ .Values.qdrantHost }}
- name: DATABASE_TYPE
value: {{ .Values.database.type }}
- name: DATABASE_USER
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: username
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: password
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: hostname
- name: DATABASE_PORT
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: db_port
- name: DATABASE_NAME
value: {{ .Values.database.name }}
- name: WEBUI_NAME
value: "Personal - Open WebUI"
- name: ENABLE_EVALUATION_ARENA_MODELS
value: "False"
- name: ENABLE_WEB_SEARCH
value: "True"
- name: WEB_SEARCH_RESULT_COUNT
value: "3"
# Values can be brave or brave_llm_context
- name: WEB_SEARCH_ENGINE
value: "brave_llm_context"
- name: BRAVE_SEARCH_API_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Namespace }}-secret
key: brave_api_key
# BRAVE_SEARCH_CONTEXT_TOKENS Only for brave_llm_context
- name: BRAVE_SEARCH_CONTEXT_TOKENS
value: "8192"
- name: WEB_SEARCH_CONCURRENT_REQUESTS
value: "1"
- name: ENABLE_IMAGE_GENERATION
value: "False"
- name: ENABLE_IMAGE_PROMPT_GENERATION
value: "False"
- name: ENABLE_IMAGE_UPLOAD
value: "False"
- name: ENABLE_IMAGE_DOWNLOAD
value: "False"
ports:
- containerPort: {{ .Values.port }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
volumeMounts:
- name: data
mountPath: /app/backend/data
readOnly: false
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ .Release.Namespace }}-data-pvc templates/secret.yaml Secret resources for values managed outside Vault.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: {{ .Release.Namespace }}-external-secret
namespace: {{ .Release.Namespace }}
spec:
refreshInterval: 24h
secretStoreRef:
kind: ClusterSecretStore
name: vault-cluster-secret-store
target:
name: {{ .Release.Namespace }}-secret
creationPolicy: Owner
data:
## Database
- secretKey: password
remoteRef:
key: postgres
property: user_password
- secretKey: username
remoteRef:
key: postgres
property: user_name
- secretKey: hostname
remoteRef:
key: postgres
property: internal_host_name
- secretKey: db_port
remoteRef:
key: postgres
property: port
# Open-WebUi
- secretKey: open_webui_secret_key
remoteRef:
key: open-webui
property: secret-key
- secretKey: brave_api_key
remoteRef:
key: genai
property: brave-api-key
- secretKey: openvino_api_key
remoteRef:
key: genai
property: openvino-api-key templates/service.yaml ClusterIP Service fronting the workload.
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Namespace }}-service
namespace: {{ .Release.Namespace }}
spec:
type: ClusterIP
selector:
app: {{ .Release.Namespace }}
ports:
- name: port
protocol: TCP
port: {{ .Values.port }}
targetPort: {{ .Values.port }} Decision. One chat UI for every model
Alternative. Per-application UIs
Why. Prompts, history, and RAG configuration stay in one place instead of scattering across tools.