Skip to content

Caddy

A config-driven Caddy reverse proxy for TypeKro. Unlike the Caddy ingress-controller (which watches Ingress resources) or a Helm bootstrap, this runs the official caddy image with a Caddyfile you supply and emits a ConfigMap (the Caddyfile) + Deployment + Service + PVC.

Its headline feature is tls internal: Caddy's built-in local CA issues valid certificates for any name — including private TLDs like *.acme.internal — with no cert-manager, no ACME, and no etcd. The PVC persists /data so the CA root is stable across restarts; trust that root once and every site is green-locked.

ts
import { caddyIngress, renderCaddyfile } from 'typekro/caddy';

What gets created

ResourcePurpose
ConfigMapholds the Caddyfile (spec.caddyfile)
PersistentVolumeClaimpersists /data — the tls internal CA root + issued certs
Deploymentruns caddy:<version> mounting the Caddyfile + the PVC — single replica, Recreate strategy
Serviceexposes the proxy (ClusterIP by default)
Namespacethe Caddy workload namespace

Single replica by design. The tls internal CA lives in a ReadWriteOnce /data PVC that exactly one pod can own, and a shared CA across pods would need ReadWriteMany storage or an externalized CA. So there is no replicaCount knob (the schema rejects it), the Deployment is pinned to one replica, and it uses the Recreate update strategy (a RollingUpdate would surge a second pod that can't co-mount the RWO PVC and wedge the rollout). HA is deliberately out of scope.

Quick example

ts
import { caddyIngress, renderCaddyfile } from 'typekro/caddy';

// Build the Caddyfile from concrete host -> upstream routes.
const caddyfile = renderCaddyfile([
  { host: 'dagster-dev.acme.internal', upstream: 'dagster.dagster-platform-dev.svc:80' },
  { host: 'signoz.acme.internal', upstream: 'signoz.observability.svc:3301' },
]);
// =>
//   dagster-dev.acme.internal {
//       tls internal
//       reverse_proxy dagster.dagster-platform-dev.svc:80
//   }
//   ...

const factory = caddyIngress.factory('direct', { namespace: 'caddy-system' });
await factory.deploy({ name: 'caddy', namespace: 'caddy-system', caddyfile });

renderCaddyfile(routes, options?)

A pure helper that turns host→upstream routes into a Caddyfile string. Kept separate from the composition so the composition stays a string passthrough (KRO-safe — see below).

  • tls: 'internal' (default) — each site uses Caddy's local CA (tls internal).
  • tls: 'off' — sites are addressed as http://<host> (plain HTTP, no auto-HTTPS).

Configuration

FieldDefaultNotes
namerequired
caddyfilerequired; the full Caddyfile content (a string)
namespacecaddy-systemCaddy workload namespace
imagecaddy:2.11.2full container image ref including the tag (one field — not image:version — so the KRO default applies cleanly)
version2.11.2version label/status hint (app.kubernetes.io/version + status.version); cosmetic — set it to match your image tag
httpPort / httpsPort80 / 443Service + container ports
serviceTypeClusterIPreach it via a tunnel; no public LB by default
persistence.size1Gisize of the always-created /data PVC
persistence.storageClasscluster defaultstorage class for the PVC
resourcescontainer requests/limits

Why the Caddyfile is a string (KRO safety)

The composition takes the Caddyfile as a string, not a structured routes array. In KRO mode a routes array would be a graph proxy that can't be .map()-ed into a string at graph-generation time. Passing the rendered string keeps the composition identical in direct and KRO modes. Build the string with renderCaddyfile() wherever you have concrete routes (e.g. a generator that knows its services), then pass it as caddyfile.

Readiness & status

FieldMeaning
readythe Deployment's one pod is ready (readyReplicas >= spec.replicas); cannot report ready before that pod exists
versionthe deploy-time version label (static, not runtime)

Factory modes

caddyIngress.factory('kro', …) generates a ResourceGraphDefinition; caddyIngress.factory('direct', …) applies the resources directly. toYaml() renders the RGD for GitOps.

Prerequisites

  • A default StorageClass (or set persistence.storageClass) so the /data PVC binds.
  • To get a green lock in a browser/client, trust Caddy's internal CA root once (Caddy serves it; it's also in the PVC under /data/caddy/pki/authorities/local/root.crt).

Next steps

Released under the Apache 2.0 License.