Not yet applied to a cluster. These manifests are written and reviewed but have never been run — treat them as a design artefact, not a verified deployment. The steps below are what to run to validate them; until someone does, assume there is a typo waiting.
You do not need a paid cluster to demonstrate this — you need manifests that apply cleanly and an explanation of why each choice was made.
k3d cluster create documind -p "8080:80@loadbalancer"
# Postgres with pgvector, for local testing only — production uses managed
# Postgres (Neon), not a pod.
kubectl create deployment postgres --image=pgvector/pgvector:pg16
kubectl set env deployment/postgres \
POSTGRES_USER=documind POSTGRES_PASSWORD=documind POSTGRES_DB=documind
kubectl expose deployment postgres --port=5432
kubectl apply -f k8s/config.yaml # edit the Secret first
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yaml
kubectl rollout status deployment/documind-api
kubectl port-forward svc/documind-api 8000:80
curl localhost:8000/healthTwo probes, two endpoints. /live never touches Postgres; /health does.
A database outage should pull pods out of the Service (readiness) without
restarting them (liveness). Pointing both probes at a database-checking endpoint
is the classic mistake that turns a recoverable dependency blip into a
CrashLoopBackOff across every replica at once.
A long startup probe. Loading bge-small and bge-reranker-base takes tens
of seconds on a cold node. The startup probe gives 120 seconds before the
liveness probe takes over, so a slow boot is not mistaken for a hang.
readOnlyRootFilesystem: true, with explicit emptyDir mounts for /tmp
and the Hugging Face cache. The cache is per-pod, so each replica downloads
weights once on first start — a shared ReadWriteMany volume would avoid that
and is the right change if pod churn is high.
HPA on CPU, scaling up fast and down slowly. CPU is a poor proxy for load here because the service spends most of its time waiting on the OpenAI API; the honest fix is a requests-per-second custom metric. Scale-down is deliberately sluggish because replacing a pod means re-downloading model weights and rebuilding a connection pool.
Migrations are not in this Deployment. alembic upgrade head belongs in a
Job or an init container gated on a lock, not in the app's start command —
otherwise replicas: 2 means two pods racing the same migration. It is left out
rather than done wrong.
- Ingress / TLS — cluster-specific, and the demo is served from Vercel.
- NetworkPolicy — worth adding for a real deployment; would be theatre here.
- A Postgres StatefulSet — managed Postgres is the right answer, and a hand-rolled stateful database in a portfolio cluster teaches the wrong lesson.