Skip to content

Controllers

A controller is a container image ctrlplane runs against your control plane. Deploy one under Controllers → Deploy a controller with a name and an image; Edit changes it later.

KUBECONFIG=/var/run/tenant/kubeconfig A kubeconfig for your control plane. controller-runtime and client-go use it automatically.
HOME=/var/run/tenant So tools that only read ~/.kube/config find the same kubeconfig.
WATCH_NAMESPACE, POD_NAMESPACE Your control plane’s namespace: the one your operator may use.
HTTPS_PROXY, HTTP_PROXY, NO_PROXY Only with a tunnel; see Networking.
/var/run/secrets/kubernetes.io/serviceaccount/namespace The namespace, where client libraries look for it.

These are set by ctrlplane; environment variables of yours with the same names are ignored.

Permissions: the controller is the service account system:serviceaccount:<namespace>:<controller>, in the group ctrlplane:controllers. It may do anything in your control plane’s namespace and with your own API types (in any namespace, since many are cluster-scoped), and record events. It has no access to built-in cluster-wide objects. That access comes with the certificate ctrlplane issues it: service accounts you create yourself don’t get it.

Most operators built with controller-runtime or kubebuilder work unchanged. Check these:

  • Runs as non-root. Controllers run as user 65532, without capabilities or privilege escalation. Images that need root won’t work.
  • Webhooks: set a webhook port and point your webhook configurations at the Service ctrlplane makes; see Webhooks.
  • Leader election works (leases in your namespace). With one replica you can leave it off.
  • Metrics: ctrlplane scrapes :8080/metrics over HTTP. kubebuilder v4 projects default to HTTPS on :8443 or no metrics: add the arguments --metrics-bind-address=:8080 and --metrics-secure=false.
  • Arguments and environment: one argument per line; environment as KEY=value lines.

  • CPU and memory limits: your controllers share their control plane’s CPU and memory. Without limits, a controller gets an even share of what’s left; set limits to split it your way. A controller that doesn’t fit isn’t started, and the site says so.

  • Probes: liveness, readiness and startup probes, as Kubernetes YAML:

    livenessProbe:
    httpGet: {path: /healthz, port: 8081}
    readinessProbe:
    httpGet: {path: /readyz, port: 8081}

    HTTP and TCP probes can’t name a host: they always check your controller’s own pod.

  • Private images: under Private image, a registry username and password or token, for example a GitHub token with read:packages for ghcr.io. The registry is taken from the image name. Credentials are stored for that controller, never shown again, and deleted with it.

Edit a controller to change its image or settings, and it rolls out the new version. If that version can’t start, the old one keeps running and the site shows why.

Removing a controller stops it and deletes its deployment and credentials. Your resources stay.

The site shows why: ImagePullBackOff (wrong image or missing credentials), CrashLoopBackOff with its last exit, or limits that don’t fit. Logs shows its output, including the run before the last restart. See Troubleshooting.