Kubernetes
coreX Platform ships a Helm chart for Kubernetes deployment. The tightly-coupled services (api, corex, coraza-spoa, and optionally varnish) run as containers in a single sidecar Pod, while PostgreSQL and Valkey can be deployed in-cluster or pointed at external managed instances.
Architecture
Sidecar Pod (StatefulSet)
api (backend), corex (HAProxy), coraza-spoa (WAF), and optionally varnish (disk cache) run as containers in one Pod. This is required because:
- Shared volume — all containers share the
haproxy-datavolume (config files, certificates, Coraza logs, VCL). - Unix socket — the API talks to HAProxy via
/var/run/haproxy.sock, which requires co-location. - localhost networking — HAProxy proxies to Coraza SPOA and Varnish via
127.0.0.1, avoiding network hops.
shareProcessNamespace: true is set so the API container can signal Coraza’s PID 1 for restarts (the Kubernetes API cannot restart a single container in a pod without rolling the whole pod).
Runtime Backend Selection
The backend uses a runtime abstraction (backend/app/services/runtime/) that auto-detects the deployment environment:
| Runtime | Used when | Behavior |
|---|---|---|
| Docker | No K8s service account token | Docker SDK for container exec, logs, restarts. |
| Kubernetes | K8s SA token present, or COREX_RUNTIME=kubernetes | Kubernetes API (in-cluster config) targeting the pod’s own containers via the downward API (COREX_POD_NAME, COREX_POD_NAMESPACE). |
| None | Neither available | Graceful degradation — falls back to local binaries. |
Leave COREX_RUNTIME=auto (default) and the backend detects the K8s SA token automatically.
HAProxy DNS Resolver
The HAProxy config’s resolvers section is parameterized via:
HAPROXY_RESOLVER_NAME(default:docker)HAPROXY_RESOLVER_NAMESERVER(default:127.0.0.11:53)
For Kubernetes the chart sets these to kube-dns and the cluster’s CoreDNS IP (default: 169.254.25.10:53). Override in values.yaml:
env:
HAPROXY_RESOLVER_NAME: "kube-dns"
HAPROXY_RESOLVER_NAMESERVER: "10.96.0.10:53" # your cluster's DNS IP
Database and Cache
Both PostgreSQL and Valkey support in-cluster (StatefulSet) and external modes:
# In-cluster (default)
postgres:
enabled: true
password: "strong-password"
# External
postgres:
enabled: false
external:
host: "db.internal.example.com"
port: 5432
database: "haproxy_manager"
user: "haproxy"
password: "strong-password"
The same pattern applies to valkey.
Prerequisites
- A running Kubernetes cluster (kind, minikube, k3s, EKS, GKE, AKS, etc.)
helm3.x installedkubectlinstalled- Docker images built and available to the cluster
1. Build Images
# Build all images
docker build -t corex-api:latest -f backend/Dockerfile .
docker build -t corex-corex:latest -f haproxy/Dockerfile .
docker build -t corex-frontend:latest -f frontend/Dockerfile .
# Load into kind
kind load docker-image corex-api:latest corex-corex:latest corex-frontend:latest
# Or load into minikube
minikube image load corex-api:latest corex-corex:latest corex-frontend:latest
For a remote cluster, push the images to a registry the cluster can pull from and set the image repos/tags in values.yaml.
2. Create a Values File
cp k8s/charts/corex-manager/values.yaml my-values.yaml
At minimum set:
secrets:
secretKey: "<python -c "import secrets; print(secrets.token_urlsafe(32))">"
adminPassword: "<strong-password>"
dataplaneApiPassword: "<strong-password>"
postgres:
password: "<strong-password>"
Do not commit my-values.yaml with real secrets. Generate secretKey with python -c “import secrets; print(secrets.token_urlsafe(32))” and use a sealed-secret or external secret for production, or reference an existingSecret instead of inline values.
3. Install the Chart
helm install corex k8s/charts/corex-manager \
-n corex \
--create-namespace \
-f my-values.yaml
Upgrade an existing release:
helm upgrade corex k8s/charts/corex-manager \
-n corex \
-f my-values.yaml
4. Access the UI
# Port-forward the frontend
kubectl port-forward svc/corex-frontend -n corex 3443:443
# Open https://localhost:3443
Log in with admin and the adminPassword from your values file.
5. Expose HAProxy
# For LoadBalancer type (cloud clusters):
kubectl get svc corex-corex -n corex
# For local clusters, use port-forward:
kubectl port-forward svc/corex-corex -n corex 8080:80 8443:443
For production, set corexService.type: LoadBalancer (cloud) or NodePort, or front the service with an Ingress resource.
Deploy Script
The deploy.py script supports two Kubernetes targets in addition to the default Docker target:
k8s-cluster (local cluster)
Builds images locally, loads them into the local cluster (kind/minikube), and runs helm upgrade:
python3 deploy.py --target k8s-cluster \
--release-name corex \
--namespace corex \
--values-file my-values.yaml
k8s-remote (remote cluster via SSH)
Rsyncs the project to a remote host, builds images on the remote, loads them into the remote cluster, and runs helm upgrade on the remote:
python3 deploy.py --target k8s-remote \
--host 1.2.3.4 --user admin \
--release-name corex \
--namespace corex \
--values-file my-values.yaml
All targets share the same selective-rebuild change detection: a manifest of file hashes is compared against the last deploy, and only services whose files changed are rebuilt.
Kubernetes-specific options
| Option | Description | Default |
|---|---|---|
--target | docker, swarm, k8s-remote, or k8s-cluster | docker |
--release-name | Helm release name | corex |
--namespace | Kubernetes namespace | corex |
--values-file | Path to Helm values.yaml override | — |
--image-tag | Docker image tag for rebuilt images | latest |
Configuration Reference
See k8s/charts/corex-manager/values.yaml in the corex_manager repo for all configurable options. Key sections:
| Section | Description |
|---|---|
image.* | Container image repos/tags/pull policies |
corexPod.* | Sidecar pod config (resources, capabilities, sysctls) |
diskCache.enabled | Toggle Varnish disk cache sidecar |
haproxyData.persistence | PVC for the shared haproxy-data volume |
certs.persistence | PVC for certificates |
postgres.enabled | In-cluster PostgreSQL (StatefulSet) vs external |
valkey.enabled | In-cluster Valkey (StatefulSet) vs external |
cap.enabled | CAPTCHA service |
frontend.service | Frontend Service type and ports |
corexService | HAProxy Service (LoadBalancer/NodePort/ClusterIP) |
mcpGateway.enabled | MCP Gateway sidecar |
mcpServer.enabled | MCP Server sidecar |
ingress.enabled | Ingress resource |
env.* | Non-secret env vars for the api container |
secrets.* | Secret env vars (or use existingSecret) |
Ingress
Enable an Ingress resource in values.yaml:
ingress:
enabled: true
className: nginx
hosts:
- host: corex.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: corex-tls
hosts:
- corex.example.com
This fronts the frontend Service; HAProxy traffic still flows through corexService.
Backward Compatibility
The Docker Compose deployment is fully preserved:
docker-compose.ymlis unchanged.deploy.py --target docker(the default) uses the exact same rsync +docker compose build/upflow as before.- When
COREX_RUNTIME=auto(default) and no K8s service account token is present, the Docker SDK path is used exactly as before. - Parity tests verify that
DockerRuntimeproduces identical behavior to the original inline code.
Next Steps
- Docker Swarm — HA deployment on Docker Swarm
- Listeners & Backends — Configure TLS, HTTP/2, QUIC, and load balancing
- Metrics & Logging — Monitor the HAProxy and WAF dashboards
- MCP Gateway — Connect AI agents via the Model Context Protocol