Helm
The icoretech/codex-pooler Helm chart deploys Codex Pooler into Kubernetes from the iCoreTech Helm repository. It separates web traffic, background work, scheduled work, and migration work so each role can scale and restart on its own.
Use this page as the public shape of the chart. Keep environment-specific secrets, private image repository names, internal hostnames, and operational evidence out of values files and public docs.
Install the chart
Section titled “Install the chart”helm repo add icoretech https://icoretech.github.io/helmhelm repo updatehelm upgrade --install codex-pooler icoretech/codex-pooler \ --namespace codex-pooler \ --create-namespace \ --version 0.5.3 \ --values ./values.production.yamlThe same chart is also published as an OCI artifact:
helm upgrade --install codex-pooler oci://ghcr.io/icoretech/charts/codex-pooler \ --namespace codex-pooler \ --create-namespace \ --version 0.5.3 \ --values ./values.production.yamlChart shape
Section titled “Chart shape”The chart has four release roles:
app, serves HTTP withOBAN_MODE=webandPHX_SERVER=trueoban.worker, runs background jobs withOBAN_MODE=workeroban.scheduler, runs scheduled jobs withOBAN_MODE=schedulermigrations, runs release migrations before app rollout when enabled
All roles use the same image repository and tag. Only the app role serves HTTP. Worker, scheduler, and migration pods don’t expose the HTTP endpoint.
Image expectations
Section titled “Image expectations”image: repository: ghcr.io/icoretech/codex-pooler tag: "" pullPolicy: IfNotPresentThe chart defaults image.tag to its appVersion, so a pinned chart version
normally selects the matching Codex Pooler image. Set image.tag only when you
intentionally run a custom image or a different immutable release tag.
Required secrets
Section titled “Required secrets”By default, the chart expects an existing Kubernetes Secret:
secrets: create: false existingSecret: codex-pooler-secretsThat secret must provide the release values needed before Codex Pooler can read database-managed settings:
database-urlsecret-key-basetotp-encryption-keytotp-key-versionupstream-secret-keyupstream-secret-key-versionrelease-cookie, when app clustering is enabled and you use the default clustering cookie key
Don’t put upstream access tokens, Pool API keys, MCP tokens, cookies, auth.json, SMTP passwords, or raw client payloads into chart values.
Host and ingress
Section titled “Host and ingress”Set the public host through chart values:
config: host: codex-pooler.example.com
ingress: enabled: true hosts: - host: codex-pooler.example.com paths: - path: / pathType: PrefixPublic client examples should use https://codex-pooler.example.com as the deployed base URL.
Role topology
Section titled “Role topology”The default topology is conservative:
app: enabled: true replicaCount: 1
oban: worker: enabled: true replicaCount: 1 scheduler: enabled: true replicaCount: 1
migrations: enabled: trueThe migration hook runs database migrations and imports the vendored OpenAI pricing feed. The scheduler keeps scheduled background work separate from request-serving pods.
Metrics collection
Section titled “Metrics collection”The app role exposes Prometheus metrics on /metrics through the same HTTP service as /healthz and /readyz. Worker, scheduler, and migration pods don’t expose the HTTP endpoint, so app-level metrics such as BEAM memory, request rate, route latency, gateway admission, stream-buffer pressure, and Ecto query pressure are scraped from app pods only. Worker and scheduler roles still run the memory sampler and VM telemetry poller, but they don’t start the Prometheus reporter because unswept histogram samples are drained only by a /metrics scrape.
If you run Prometheus Operator, enable the chart ServiceMonitor:
monitoring: serviceMonitor: enabled: true labels: release: kube-prometheus-stack interval: 10s scrapeTimeout: 5s path: /metricsSet labels to match your Prometheus Operator selector. The common release: kube-prometheus-stack label is only an example; use the label your Prometheus installation selects.
If you configure a metrics bearer token from /admin/system, create a Kubernetes Secret containing the same raw token and point the ServiceMonitor at it:
monitoring: serviceMonitor: enabled: true bearerTokenSecret: name: codex-pooler-metrics key: tokenDon’t put the raw metrics token in Helm values. The app stores only a keyed digest, so the token must be saved separately at creation time if Prometheus will authenticate with it.
Render the monitoring resource before installing:
helm template codex-pooler icoretech/codex-pooler \ --namespace codex-pooler \ --version 0.5.3 \ --set monitoring.serviceMonitor.enabled=true \ --set monitoring.serviceMonitor.labels.release=kube-prometheus-stack \ --set monitoring.serviceMonitor.interval=10s \ --set monitoring.serviceMonitor.scrapeTimeout=5s \ --show-only templates/servicemonitor.yamlAfter installation, verify that Kubernetes rendered the ServiceMonitor and that Prometheus has active targets:
kubectl -n codex-pooler get servicemonitor codex-pooler-appkubectl -n codex-pooler get endpoints codex-pooler-appWhen you can reach the Prometheus HTTP API, check scrape health:
# If Prometheus is only reachable inside the cluster, port-forward your Prometheus service first.kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090
curl -fsS --get http://127.0.0.1:9090/api/v1/query \ --data-urlencode 'query=up{namespace="codex-pooler",job="codex-pooler-app"}'Useful first queries:
vm_memory_total_bytes{namespace="codex-pooler", job="codex-pooler-app"}sum by (pod) (rate(phoenix_endpoint_stop_count{namespace="codex-pooler", job="codex-pooler-app"}[5m]))sum by (method, status_class) (rate(codex_pooler_http_request_count{namespace="codex-pooler", job="codex-pooler-app"}[5m]))topk(10, sum by (source, command) (rate(codex_pooler_repo_query_count{namespace="codex-pooler", job="codex-pooler-app"}[5m])))For memory incidents, also collect Kubernetes cgroup metrics such as container_memory_working_set_bytes, container_memory_rss, restart counters, and OOM counters from your cluster collectors. The app metrics explain BEAM and gateway behavior; cgroup and kube-state-metrics data explain container limits, restarts, and killed pods.
The monitoring and runtime triage guide includes a starter Grafana dashboard JSON and PromQL examples.
Websocket replica caveat
Section titled “Websocket replica caveat”The chart defaults the web app to one replica. If app.replicaCount is 2 or
higher, the chart treats websocket owner forwarding as required.
The current chart guard rejects multi-replica app rendering unless app clustering is enabled and app pods participate in that cluster. App participation defaults to enabled, so the minimal multi-replica values shape is:
clustering: enabled: true
app: replicaCount: 2With that shape, the chart renders CODEX_POOLER_WEBSOCKET_OWNER_FORWARDING=true
on app pods automatically. Render the chart and verify the topology before
raising app.replicaCount.
Flux multi-replica example
Section titled “Flux multi-replica example”This example shows a Flux HelmRepository and HelmRelease for two app
replicas. It assumes:
- a Kubernetes Secret named
codex-pooler-secretsexists in that namespace - the Secret contains the required release keys plus
release-cookie - Postgres is reachable through the Secret’s
database-url https://codex-pooler.example.comis the public app URL
The example leaves chart defaults out of values. In particular, it does not set
image values, secrets.create, secrets.existingSecret, app probes, rollout
strategy, termination grace, worker or scheduler replica counts, or explicit
websocket owner-forwarding values.
apiVersion: v1kind: Namespacemetadata: name: codex-pooler---apiVersion: source.toolkit.fluxcd.io/v1kind: HelmRepositorymetadata: name: icoretech namespace: flux-systemspec: interval: 1h url: https://icoretech.github.io/helm---apiVersion: helm.toolkit.fluxcd.io/v2kind: HelmReleasemetadata: name: codex-pooler namespace: codex-poolerspec: releaseName: codex-pooler chart: spec: chart: codex-pooler version: "0.5.3" sourceRef: kind: HelmRepository name: icoretech namespace: flux-system interval: 5m timeout: 3m install: remediation: retries: -1 upgrade: remediation: retries: -1 strategy: uninstall remediateLastFailure: true values: config: host: codex-pooler.example.com ingress: enabled: true className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt hosts: - host: codex-pooler.example.com paths: - path: / pathType: Prefix tls: - secretName: codex-pooler-tls hosts: - codex-pooler.example.com monitoring: serviceMonitor: enabled: true labels: release: kube-prometheus-stack interval: 10s scrapeTimeout: 5s clustering: enabled: true participants: worker: false scheduler: false app: replicaCount: 2 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app.kubernetes.io/name: codex-pooler app.kubernetes.io/instance: codex-pooler app.kubernetes.io/component: app topologyKey: kubernetes.io/hostnameWhen app.replicaCount is 2 or higher, the chart enables websocket owner
forwarding automatically. Keep clustering.participants.worker and
clustering.participants.scheduler disabled unless those roles also need BEAM
cluster membership in your environment.
Render safely before install
Section titled “Render safely before install”Render manifests locally before applying them:
helm template codex-pooler icoretech/codex-pooler \ --version 0.5.3 \ --set config.host=codex-pooler.example.com \ --set ingress.enabled=true \ --set ingress.hosts[0].host=codex-pooler.example.comInspect the output for the expected app, worker, scheduler, migration, Secret reference, Service, and Ingress resources. The rendered app Deployment should serve HTTP. Worker, scheduler, and migration resources should not.
For install or upgrade, provide your production values file from a private path:
helm upgrade --install codex-pooler icoretech/codex-pooler \ --namespace codex-pooler \ --create-namespace \ --version 0.5.3 \ --values ./values.production.yamlKeep values.production.yaml private if it contains secret names, internal DNS names, ingress details, or environment-specific settings.