Skip to content

Kubernetes deployment

Deploy FCC on any Kubernetes cluster using the Helm chart at charts/fcc/.

This doc walks through a concrete end-to-end deployment. For reference material, see helm.md. For security defaults, see security.md.

Prerequisites

  • Kubernetes 1.25+ (tested against 1.28, 1.29, 1.30, 1.31)
  • Helm 3.14+
  • kubectl configured for your cluster
  • (Optional) an Ingress controller (nginx-ingress, Traefik, etc.)
  • (Optional) cert-manager for automatic TLS certificates

Quick install (default values)

helm install fcc oci://ghcr.io/rollingthunderfourtytwo-afk/helm/fcc --version 1.1.1

Or from a local clone:

git clone https://github.com/rollingthunderfourtytwo-afk/l2_fcc_agent_team_ext.git
cd l2_fcc_agent_team_ext
helm install fcc ./charts/fcc

This deploys the full 4-service stack (backend, frontend, streamlit, jupyter) into the current namespace using the default mock AI provider. Every pod runs as non-root with a restricted PodSecurityContext.

Watch the pods come up:

kubectl get pods -l app.kubernetes.io/instance=fcc -w

You should see 4 pods reach Running status within 30-60 seconds (assuming images are already cached).

Access the stack

Without ingress, use port-forwarding:

# Frontend SPA
kubectl port-forward svc/fcc-frontend 8080:80
# Open http://localhost:8080

# Backend health
kubectl port-forward svc/fcc-backend 8765:8765
curl http://localhost:8765/health

# Streamlit explorer
kubectl port-forward svc/fcc-streamlit 8501:8501

# JupyterLab
kubectl port-forward svc/fcc-jupyter 8888:8888

Production install

The values-prod.yaml overlay adds:

  • restart: always equivalent (Deployment/StatefulSet default)
  • CPU + memory limits sized for real workloads
  • 2 replicas for backend, frontend, streamlit (horizontal scaling)
  • Ingress enabled on the frontend with cert-manager annotations
  • Required jupyter.token (install fails if unset)
kubectl create namespace fcc

# Pre-create the secret out-of-band
kubectl create secret generic fcc-secrets \
  --namespace fcc \
  --from-literal=anthropic-api-key="$ANTHROPIC_API_KEY" \
  --from-literal=jupyter-token="$(openssl rand -hex 32)"

# Install with production overlay
helm install fcc ./charts/fcc \
  --namespace fcc \
  --values ./charts/fcc/values-prod.yaml \
  --set-string global.ai.anthropicApiKey="$ANTHROPIC_API_KEY" \
  --set-string jupyter.token="$(kubectl -n fcc get secret fcc-secrets -o jsonpath='{.data.jupyter-token}' | base64 -d)" \
  --set frontend.ingress.hosts[0].host=fcc.example.com

Scaling

Backend, frontend, and streamlit are all horizontally scalable. Scale via helm upgrade:

helm upgrade fcc ./charts/fcc \
  --namespace fcc \
  --reuse-values \
  --set backend.replicas=5

Note: the jupyter StatefulSet stays at replicas: 1 because its PVC is ReadWriteOnce. If you need multiple concurrent users, deploy multiple Helm releases with different release names:

helm install fcc-alice ./charts/fcc --set jupyter.token=... -n fcc-alice
helm install fcc-bob ./charts/fcc --set jupyter.token=... -n fcc-bob

Health and monitoring

The backend's /health endpoint is probed by Kubernetes via a TCP socket probe (the WS bridge uses the websockets library's process_request callback to serve HTTP 200 on /health):

kubectl exec -it deploy/fcc-backend -- curl -s http://localhost:8765/health
# {"status":"ok","service":"fcc-ws-bridge"}

For production observability, enable the observability stack:

helm upgrade fcc ./charts/fcc \
  --namespace fcc \
  --reuse-values \
  --set backend.env.OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4318

See observability.md for the full tracing / metrics setup.

AI provider configuration

Set the default provider globally:

# Hosted Anthropic
helm upgrade fcc ./charts/fcc \
  --namespace fcc \
  --reuse-values \
  --set global.ai.defaultProvider=anthropic \
  --set-string global.ai.anthropicApiKey="$ANTHROPIC_API_KEY"

# Self-hosted vLLM (requires vLLM pods in the same cluster)
helm upgrade fcc ./charts/fcc \
  --namespace fcc \
  --reuse-values \
  --set global.ai.defaultProvider=vllm \
  --set global.ai.vllmBaseUrl=http://vllm-server.vllm.svc.cluster.local:8000/v1

# Local Ollama (requires Ollama pod or external Ollama service)
helm upgrade fcc ./charts/fcc \
  --namespace fcc \
  --reuse-values \
  --set global.ai.defaultProvider=ollama \
  --set global.ai.ollamaBaseUrl=http://ollama.ollama.svc.cluster.local:11434/v1

Upgrade

See upgrade.md for the full rolling-upgrade procedure.

Quick version:

helm repo update
helm upgrade fcc ./charts/fcc --reuse-values --version 1.1.2

Uninstall

helm uninstall fcc --namespace fcc

The jupyter PVC is not automatically deleted (to preserve notebook content). To remove completely:

kubectl delete pvc -l app.kubernetes.io/instance=fcc -n fcc
kubectl delete namespace fcc

Troubleshooting

ImagePullBackOff
Verify your cluster can reach ghcr.io: kubectl run --rm -it test --image=ghcr.io/rollingthunderfourtytwo-afk/fcc-backend:1.1.1 --command -- /bin/true
Backend pod CrashLoopBackOff on first install
Check logs: kubectl logs deploy/fcc-backend. Most common cause: the websockets library version is too old. The v1.1.1 image pins websockets>=12.0.
Frontend shows empty page or WebSocket errors
The React app proxies /ws to fcc-backend:8765. Verify the backend Service DNS works: kubectl exec deploy/fcc-frontend -- wget -qO- http://fcc-backend:8765/health
StatefulSet stuck at 0/1 ready (jupyter)
Check the PVC status: kubectl get pvc -l app.kubernetes.io/component=jupyter. If pending, your cluster likely has no default StorageClass — set jupyter.persistence.storageClassName explicitly.

See also