Skip to content

Instantly share code, notes, and snippets.

@Amoenus
Created December 17, 2025 07:18
Show Gist options
  • Select an option

  • Save Amoenus/325f49b8b8058d2c1ba5ea43fcfe5a6d to your computer and use it in GitHub Desktop.

Select an option

Save Amoenus/325f49b8b8058d2c1ba5ea43fcfe5a6d to your computer and use it in GitHub Desktop.
Dot-Ai deployment with HttpRoute
{{/*
Expand the name of the chart.
*/}}
{{- define "mcp-server.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mcp-server.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart labels.
*/}}
{{- define "mcp-server.labels" -}}
helm.sh/chart: {{ include "mcp-server.name" . }}
{{ include "mcp-server.selectorLabels" . }}
app.kubernetes.io/part-of: infrastructure
homelab.tier: infrastructure
homelab.category: ai
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Create the name of the service account to use.
*/}}
{{- define "mcp-server.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mcp-server.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Selector labels.
*/}}
{{- define "mcp-server.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mcp-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
apiVersion: v2
name: mcp-server
description: MCP Server for AI agent integration
type: application
version: 0.1.0
appVersion: "1.0"
dependencies:
- name: qdrant
version: 1.16.2
repository: https://qdrant.github.io/qdrant-helm
condition: qdrant.enabled
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mcp-server.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mcp-server.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- toYaml .Values.podAnnotations | nindent 8 }}
labels:
{{- include "mcp-server.selectorLabels" . | nindent 8 }}
homelab.tier: infrastructure
homelab.category: ai
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "mcp-server.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
env:
- name: QDRANT_URL
value: "http://{{ .Values.qdrant.fullnameOverride }}:6333"
- name: DOT_AI_SESSION_DIR
value: "/tmp/sessions"
- name: TRANSPORT_TYPE
value: "http"
- name: SESSION_MODE
value: {{ .Values.mcp.sessionMode | default "stateless" | quote }}
- name: OTEL_SERVICE_VERSION
value: "{{ .Values.image.tag | default .Chart.AppVersion }}"
# AI Provider Configuration
- name: AI_PROVIDER
value: "google"
- name: EMBEDDINGS_PROVIDER
value: "google"
# API Keys from External Secret
- name: GOOGLE_GENERATIVE_AI_API_KEY
valueFrom:
secretKeyRef:
name: mcp-server-gemini-api-key
key: GOOGLE_API_KEY
- name: GOOGLE_API_KEY
valueFrom:
secretKeyRef:
name: mcp-server-gemini-api-key
key: GOOGLE_API_KEY
volumeMounts:
- name: sessions
mountPath: /tmp/sessions
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: sessions
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mcp-server-gemini-api-key
namespace: mcp-server
labels:
app.kubernetes.io/name: mcp-server
app.kubernetes.io/component: external-secret
app.kubernetes.io/managed-by: argocd
homelab.tier: infrastructure
homelab.category: ai
spec:
refreshInterval: 15s
secretStoreRef:
name: bitwarden-secretsmanager
kind: ClusterSecretStore
target:
name: mcp-server-gemini-api-key
creationPolicy: Owner
data:
- secretKey: GOOGLE_API_KEY
remoteRef:
key: changeme-0000-0000-0000-000000000000 # Your Bitwarden secret ID
{{- if .Values.ingress.enabled }}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "mcp-server.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
app.kubernetes.io/component: route
annotations:
external-dns.alpha.kubernetes.io/hostname: {{ .Values.ingress.hostname }}
external-dns.alpha.kubernetes.io/target: example.com
external-dns.alpha.kubernetes.io/ttl: "300"
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec:
parentRefs:
- name: homelab-gateway
namespace: infrastructure
hostnames:
- {{ .Values.ingress.hostname }}
rules:
- matches:
- path:
type: PathPrefix
value: {{ .Values.ingress.path }}
backendRefs:
- name: {{ include "mcp-server.fullname" . }}
port: {{ .Values.service.port }}
weight: 1
{{- end }}
{{- if .Values.serviceAccount.create -}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "mcp-server.fullname" . }}-role
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
rules:
# Basic cluster discovery permissions
- apiGroups: [""]
resources: ["namespaces", "nodes", "pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways", "httproutes"]
verbs: ["get", "list", "watch"]
# Kyverno policy access
- apiGroups: ["kyverno.io"]
resources: ["clusterpolicies", "policies"]
verbs: ["get", "list", "watch"]
# Crossplane resources (if used)
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "mcp-server.fullname" . }}-binding
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ include "mcp-server.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: {{ include "mcp-server.fullname" . }}-role
apiGroup: rbac.authorization.k8s.io
{{- end }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "mcp-server.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mcp-server.selectorLabels" . | nindent 4 }}
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mcp-server.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mcp-server.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
---
# Default values for mcp-server.
replicaCount: 1
image:
repository: ghcr.io/vfarcic/dot-ai
pullPolicy: IfNotPresent
# Overridden by Chart AppVersion
tag: 0.145.1@sha256:bf2f89a3137ffbf542c6db5aa35de4d0127901427c0fe0460426a0e40f02776c
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
service:
type: ClusterIP
port: 3456
# HTTPRoute configuration (replaces ingress)
ingress:
enabled: true
hostname: dot-ai.example.com
path: /
# MCP Server configuration
mcp:
sessionMode: stateless
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
nodeSelector:
kubernetes.io/arch: amd64
homelab.ebpf: "true"
tolerations: []
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
# Qdrant dependency
qdrant:
enabled: true
fullnameOverride: mcp-server-qdrant
# Qdrant specific configuration
service:
type: ClusterIP
port: 6333
persistence:
enabled: true
size: 10Gi
storageClass: local-path
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment