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:

  1. Shared volume — all containers share the haproxy-data volume (config files, certificates, Coraza logs, VCL).
  2. Unix socket — the API talks to HAProxy via /var/run/haproxy.sock, which requires co-location.
  3. 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:

RuntimeUsed whenBehavior
DockerNo K8s service account tokenDocker SDK for container exec, logs, restarts.
KubernetesK8s SA token present, or COREX_RUNTIME=kubernetesKubernetes API (in-cluster config) targeting the pod’s own containers via the downward API (COREX_POD_NAME, COREX_POD_NAMESPACE).
NoneNeither availableGraceful 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.)
  • helm 3.x installed
  • kubectl installed
  • 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>"
Generate secrets out of band

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

OptionDescriptionDefault
--targetdocker, swarm, k8s-remote, or k8s-clusterdocker
--release-nameHelm release namecorex
--namespaceKubernetes namespacecorex
--values-filePath to Helm values.yaml override
--image-tagDocker image tag for rebuilt imageslatest

Configuration Reference

See k8s/charts/corex-manager/values.yaml in the corex_manager repo for all configurable options. Key sections:

SectionDescription
image.*Container image repos/tags/pull policies
corexPod.*Sidecar pod config (resources, capabilities, sysctls)
diskCache.enabledToggle Varnish disk cache sidecar
haproxyData.persistencePVC for the shared haproxy-data volume
certs.persistencePVC for certificates
postgres.enabledIn-cluster PostgreSQL (StatefulSet) vs external
valkey.enabledIn-cluster Valkey (StatefulSet) vs external
cap.enabledCAPTCHA service
frontend.serviceFrontend Service type and ports
corexServiceHAProxy Service (LoadBalancer/NodePort/ClusterIP)
mcpGateway.enabledMCP Gateway sidecar
mcpServer.enabledMCP Server sidecar
ingress.enabledIngress 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.yml is unchanged.
  • deploy.py --target docker (the default) uses the exact same rsync + docker compose build/up flow 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 DockerRuntime produces identical behavior to the original inline code.

Next Steps