Skip to content

Kubernetes deployment

foxctl ships in-repo Kubernetes manifests in deploy/kubernetes/ with a Kustomize base and overlays for different runtime and storage models.

deploy/kubernetes/
base/ # Base manifest layer (legacy defaults)
deployment.yaml # Main foxctl deployment (3 replicas)
configmap.yaml # Base env vars
secrets.yaml # Secret references
service.yaml # Service exposure
ingress.yaml # Ingress routing
workspace-deployment.yaml # Optional workspace pod with git-sync
embedding-worker.yaml # Background embedding worker
cronjobs.yaml # Scheduled maintenance workloads
rbac.yaml # Service account and RBAC
network-policy.yaml # Network isolation
pdb.yaml # Pod disruption budget
hpa.yaml # Horizontal pod autoscaler
overlays/
local/ # Single-node dev layout
postgres/ # Production PostgreSQL + S3 CAS
public-gui/ # GUI hosting with Better Auth gateway
public-gui-local/ # Local GUI testing variant
OverlayUse CaseStorageProbes
overlays/localDevelopment, single-node testingSQLite local, emptyDirexec
overlays/postgresProduction shared statePostgreSQL + S3 CASHTTP /healthz, /readyz
overlays/public-guiPublic gui-agent hostingPostgreSQL + S3 CAS + auth gatewayHTTP
flowchart LR
Internet[Users / APIs] --> Ingress
Ingress --> Foxctl["foxctl Deployment\n(web API + websocket + optional chat adapter)"]
Foxctl --> Store[Logical stores via dbdriver]
Store --> PostgreSQL[(PostgreSQL)]
Store --> SQLite[(SQLite local fallback)]
Foxctl --> CAS[(CAS: s3/sqlite/file)]
Foxctl --> Runtime["legacy agent runtime + v2 services"]
Runtime --> Jido["optional Jido bridge"]
Foxctl --> Observability["/api/events / logs / probes"]

The web pod is the main entrypoint, serving:

  • /api/* — application routes
  • /ws/console/* — websocket connections
  • Optional chat adapter ingress (Discord, Telegram, Teams)
Terminal window
kubectl apply -k deploy/kubernetes/overlays/local

This runs a single pod with local state in emptyDir, suitable for development and testing.

Terminal window
kubectl apply -k deploy/kubernetes/overlays/postgres

This overlay configures:

  • FOXCTL_DB_DRIVER: "postgres" with connection from a Kubernetes secret
  • FOXCTL_CAS_DRIVER: "s3" with S3 bucket and region
  • HTTP health probes (/healthz for liveness, /readyz for readiness)
Terminal window
kubectl apply -k deploy/kubernetes/overlays/public-gui

Composes the PostgreSQL overlay and fronts the private foxctl service with a Better Auth gateway for public gui-agent access.

The base manifests in deploy/kubernetes/base/ provide a working foundation with legacy defaults. These defaults are not the recommended production setup — use an overlay to override them.

The core deployment.yaml runs 3 replicas with:

  • Security: runs as non-root user (UID 1000), read-only root filesystem, all capabilities dropped
  • Resource limits: 256Mi–1Gi memory, 100m–500m CPU
  • Probes: exec-based foxctl health (liveness) and foxctl health --ready (readiness)
  • Observability: FOXCTL_OBS_DIR=/var/log/foxctl with emptyDir volume
  • Pod anti-affinity for availability zone spread
  • Graceful shutdown with foxctl drain preStop hook

The base ConfigMap defaults to:

  • FOXCTL_DB_DRIVER: turso (legacy — production uses postgres)
  • Older CAS key names (FOXCTL_CAS_BACKEND, FOXCTL_CAS_BUCKET)

The PostgreSQL overlay replaces these with current keys.

Secrets referenced by the deployment:

  • turso-url, turso-token — Turso database connection (legacy)
  • embedding-base-url, embedding-api-key — Embedding provider
  • rerank-base-url, rerank-api-key — Reranking provider
  • oauth-enc-key — OAuth encryption key

workspace-deployment.yaml provides an optional dedicated pod with a git-sync sidecar that mounts source code into the foxctl pod’s workspace.

embedding-worker.yaml runs a separate worker deployment for background embedding pipeline operations, with its own resource profile and probe configuration.

deploy/kubernetes/overlays/postgres/ updates the base for production use:

FOXCTL_DB_DRIVER: "postgres"
FOXCTL_POSTGRES_MAX_CONNS: "20"
FOXCTL_POSTGRES_REQUIRE_VECTOR: "true"
FOXCTL_CAS_DRIVER: "s3"
FOXCTL_CAS_S3_BUCKET: "foxctl-cas-production"
  • Sets FOXCTL_POSTGRES_DSN from a Kubernetes secret
  • Replaces exec probes with HTTP probes:
    • GET /healthz (liveness)
    • GET /readyz (readiness)

This matches the current server behavior where foxctl web serve exposes these endpoints at the root level.

VariablePurpose
FOXCTL_DB_DRIVERStorage driver: postgres, turso, or sqlite
FOXCTL_POSTGRES_DSNPostgreSQL connection string (from secret)
FOXCTL_POSTGRES_MAX_CONNSMax database connections
FOXCTL_POSTGRES_REQUIRE_VECTORRequire pgvector extension
FOXCTL_CAS_DRIVERCAS driver: s3, sqlite, or file
FOXCTL_CAS_S3_BUCKETS3 bucket name
FOXCTL_CAS_S3_REGIONS3 region
FOXCTL_WORKSPACEWorkspace root path
FOXCTL_STORAGE_ROOTStorage root directory
FOXCTL_OBS_DIRObservability output directory
FOXCTL_EMBEDDING_PROVIDEREmbedding provider name
FOXCTL_EMBEDDING_MODELEmbedding model name
FOXCTL_EMBEDDING_BASE_URLEmbedding API base URL
FOXCTL_EMBEDDING_API_KEYEmbedding API key
FOXCTL_RERANK_PROVIDERReranking provider
FOXCTL_JIDO_*Jido orchestration configuration (optional)
VariableReplacement
FOXCTL_CAS_BACKENDFOXCTL_CAS_DRIVER
FOXCTL_CAS_BUCKETFOXCTL_CAS_S3_BUCKET

Chat adapters are selected at runtime via CLI flags, not in manifests:

Terminal window
foxctl web serve --chat discord
foxctl web serve --chat telegram
foxctl web serve --chat teams

For Teams webhooks, the endpoint POST /api/teams/messages must be reachable through your ingress and network policies.

For horizontal scaling, you need shared control-plane state:

  • PostgreSQL-backed stores for sessions, agents, and coordination state
  • S3 CAS for shared artifact storage
  • PostgreSQL companion turn lock when available

Local SQLite mode works for dev and small deployments but does not provide shared-state semantics across replicas.

The base manifests use exec probes:

livenessProbe:
exec:
command: ["foxctl", "health"]
readinessProbe:
exec:
command: ["foxctl", "health", "--ready"]

The PostgreSQL overlay switches to HTTP probes:

livenessProbe:
httpGet:
path: /healthz
port: 8080
readinessProbe:
httpGet:
path: /readyz
port: 8080

Before deploying to production:

  • Confirm which overlay matches your storage strategy
  • Confirm database driver and secret source are correct
  • Confirm CAS driver and object storage configuration
  • Confirm network policies allow required traffic
  • Confirm observability directory is writable and has retention
  • Confirm resource limits match your workload
  • Verify rollback path before applying new manifests
  • If using chat adapters, confirm the webhook endpoint is reachable

Jido-backed orchestration requires additional FOXCTL_JIDO_* environment variables beyond the base manifests. These are not broadly documented in the base manifests yet. Enable only when you need the Jido bridge for advanced orchestration patterns.

The runtime behind the web pod is hybrid:

  • Legacy mailbox-driven agent runtime coexists with v2 projection and orchestration surfaces
  • Context and observability subsystems run as part of the same process
  • The embedding worker can run as a separate deployment for background processing