Blog · September 16, 2025 · Updated on September 7, 2026 · 8 min read

Kubernetes Operator and CRD Explained

Open gold pocket watch revealing the exposed movement with gears against a black background
Photo: Felix Mittermeier / Pexels

A Kubernetes operator is a controller that takes over a specific operational task automatically by reacting to a Custom Resource Definition (CRD). The CRD extends the Kubernetes API with your own object type, one you can then create and query with kubectl just like any built-in object. The operator watches instances of that type and makes sure the cluster keeps matching whatever is described inside them.

I run Kubernetes clusters in production and wrote the Kubernetes practical guide published by Rheinwerk (2024), with its own chapter on CRDs and operators. You can find all my Kubernetes articles collected on the Kubernetes page.

What a Custom Resource Definition is and what it is for

Without a CRD, Kubernetes only knows its built-in objects: Pod, Deployment, Service, and so on. A CRD registers a new object type with the API server, complete with a name, a structure, and validation rules. After that, the type behaves like any other Kubernetes object: you can create, change, delete, and list it with kubectl, including kubectl get.

You could store the same information in a ConfigMap instead, but you would lose three things a CRD gives you: schema validation that rejects bad input right when it is created, a proper, named API instead of a generic key-value store, and the ability for a controller to react specifically to changes of that exact type. That third property is the foundation for the operator described below.

A real-world example you probably already have in your cluster without noticing: the Prometheus operator ships its own CRDs such as ServiceMonitor and PrometheusRule. You create a ServiceMonitor object instead of hand-editing a Prometheus configuration, and the operator translates that in the background into the matching scrape configuration. More on that in Kubernetes Monitoring with Prometheus.

From schema to your own custom resource

Say you want to describe recurring backups of individual applications in the cluster declaratively, instead of maintaining every backup as its own cron script. First you create a CRD that describes the BackupSchedule object type:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: backupschedules.ops.example.com
spec:
  group: ops.example.com
  names:
    kind: BackupSchedule
    plural: backupschedules
    singular: backupschedule
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                target:
                  type: string
                cronSchedule:
                  type: string
                retentionDays:
                  type: integer
              required: ["target", "cronSchedule"]

The group field decides which API group the new type lives under, names.kind is the CamelCase name you use in your own manifests, and names.plural is what you will later need for kubectl get backupschedules. Under schema, you define which fields an instance may have and what type they must be. Once this CRD is in the cluster, you can create a concrete instance:

apiVersion: ops.example.com/v1
kind: BackupSchedule
metadata:
  name: shop-database
spec:
  target: "shop-postgres"
  cronSchedule: "0 3 * * *"
  retentionDays: 14

Without a controller reading this custom resource, nothing actually happens yet. Kubernetes stores the object and lets you query it, but it does not run any backups on its own. That is what the operator does next.

Validation in the CRD: schema and CEL rules

A plain type check is rarely enough. In this example, you want to prevent anyone from setting a retention period of zero or several thousand days, or an invalid cron expression. Besides upper and lower bounds for numbers, Kubernetes has, for a while now, also supported rules in the Common Expression Language (CEL), written into the schema under x-kubernetes-validations, which compare field values against each other, for example checking that an end date falls after a start date.

A simple bound for the retentionDays field looks like this:

retentionDays:
  type: integer
  minimum: 1
  maximum: 365

With this in place, the API server rejects any custom resource whose retention period falls outside that range before it is ever stored in the cluster. Whoever creates the CR gets the error back immediately, rather than the operator failing on it later. Details on the available validation options are in the Kubernetes documentation on custom resources.

What a Kubernetes operator is: controller and reconciliation

This is the distinction that gets blurred most often: a CRD on its own is not an operator. It only describes the object type and its rules, it does nothing. What turns a CRD into an operator is the controller that reconciles the desired state from the custom resource with the actual state in the cluster. Both parts together, the object type and the reconciling controller, make up the operator pattern; without the second one you have a well validated place to store data in the cluster and nothing else.

The operator itself is not a Kubernetes object but an application running in the cluster that listens for changes to your custom resource through the Kubernetes API. As soon as a BackupSchedule is created, changed, or deleted, the controller springs into action and compares the desired state from the CR with what actually exists in the cluster, here a running CronJob for the backup.

This comparison and the resulting adjustment is called reconciliation, the same loop a ReplicaSet uses to keep the number of running pods in check. An operator applies that same pattern only to the area you defined yourself: it creates the CronJob when needed, updates it when the retention period changes, and removes it again once the BackupSchedule is deleted. That way, the logic that would otherwise live in a separate script or a manual routine disappears entirely into the cluster itself. How to build your own operator is covered in the Kubernetes documentation on the operator pattern.

Operator or Helm? When you need which tool

Helm and operators get confused often, because both deal with Kubernetes applications. The difference is in when they become active.

Question Helm Operator
What it does renders templates and applies them once continuously watches state and reconciles it
When it is enough the state stays stable after rollout, until the next upgrade the state has to repair itself, for example after a failure
Typical example a chart for a stateless web application a database cluster with automatic failover
Development effort write templates and values, no custom code a custom controller, usually built with Kubebuilder or the Operator SDK

In practice the two are not mutually exclusive. An operator is often installed through a Helm chart itself, because the chart handles installing the CRD and the controller, while the operator then carries the actual operational logic afterward. More on Helm itself is in Helm Charts Explained: Packages for K8s.

A practical example: the Zalando Postgres operator

An operator that shows how much operational work a good CRD design can take off your hands is the open source Zalando Postgres operator. Instead of running a Postgres database as a StatefulSet by hand and writing your own scripts for user creation, permission management, and failover, you use a custom resource to describe only the number of instances, the desired databases and users, and the Postgres version. The operator's controller handles the rest: it creates the instances, manages access rights, and takes care of failover when an instance goes down.

That is the core of the operator pattern: recurring, expert-heavy operational tasks move out of individual people's heads and into a custom resource that anyone on the team can read and change, without needing to know the details of Postgres administration.

Frequently asked questions

What is a Custom Resource Definition in Kubernetes?

A Custom Resource Definition extends the Kubernetes API with your own object type and its own schema. Once registered, that type behaves like a built-in Kubernetes object: you create instances of it, query them with kubectl, and Kubernetes validates them against the schema from the CRD.

What is the operator pattern in Kubernetes?

The operator pattern describes a controller that reacts to a custom resource and continuously reconciles the actual state of the cluster with the desired state described in that custom resource. It effectively turns human operational knowledge into automated code running inside the cluster.

Do I need an operator, or is Helm enough?

If a one-time rollout that stays stable afterward is enough, Helm covers it. If an application has to repair itself, for example during a database failover or a required version migration, you need an operator that keeps running continuously instead of only acting during rollout.

Can I use a CRD without building my own operator?

Yes. A CRD alone already gives you a validated, dedicated API and a place to store data in the cluster, with no controller at all. It only becomes genuinely useful once something reacts to changes, though, otherwise the custom resource stays a carefully described but inert object.

Where to go next

If you use CRDs in your cluster, it is worth checking who is allowed to create them: Kubernetes Admission Controllers Explained covers how to enforce additional rules for objects in the cluster, including your own custom resources. And if an operator such as the Prometheus operator is already part of your monitoring, Kubernetes Monitoring with Prometheus is the right place to start.

My suggestion for getting started: run kubectl get crd and see which CRDs already sit in your cluster, often more than you would expect, since many tools install them in the background. That gives you a sense of just how common the pattern already is.

In full detail, from validation to the architecture of a custom controller, this is covered in chapter 5 of my Kubernetes Practical Guide (Rheinwerk Computing).

Kevin Welter

Kevin Welter

Developer, IT architect, author of technical books (Kubernetes, cloud infrastructures) and speaker. Runs his business with an AI workforce of eight AI employees and shows solo business owners in his community how to hire their first AI employee.

More about AI employees

Kubernetes from the basics to a production-ready cluster

Get the book