Articles

What Are Kubernetes CRDs? Custom Resources Explained

A Kubernetes CRD (CustomResourceDefinition) extends the API with new resource types, letting the cluster manage custom objects like native ones.

The Lycoris Team The Lycoris Team · · 5 min read
Kubernetes logo icon

A CustomResourceDefinition (CRD) is how you teach Kubernetes about a new kind of object. Kubernetes ships with built-in resource types — Pods, Deployments, Services — but a CRD lets you register your own, such as Database or Certificate, and have the Kubernetes API server store, validate, and serve them exactly like it does the built-in ones, complete with kubectl get support and versioning.

What problem CRDs solve

Kubernetes is fundamentally a declarative API and a control loop that reconciles actual state toward desired state — that’s how Pods, Deployments, and Services already work. The insight behind CRDs is that this same pattern is useful for things Kubernetes doesn’t know about out of the box: a managed database cluster, a TLS certificate that needs renewing, a message queue topic, or an internal platform’s notion of an “application.” Rather than building a separate system to declare and reconcile that state, CRDs let you extend the Kubernetes API itself to understand it.

Without CRDs, teams either bolt state onto ConfigMaps and annotations — which the API server can’t validate or version properly — or run an entirely separate control plane alongside Kubernetes. CRDs give custom objects the same first-class treatment as native resources: schema validation, RBAC rules via Kubernetes RBAC, API versioning, and kubectl support, all without writing an API server from scratch.

CRD vs the controller that acts on it

Registering a CRD only extends the API’s vocabulary — it doesn’t make anything happen on its own. Creating a Database custom resource just stores a Database object in etcd, the same as any other resource; nothing provisions an actual database unless something is watching for Database objects and reconciling them.

That “something” is a controller (or operator, when the controller encodes non-trivial operational knowledge — backup schedules, failover, version upgrades). The controller watches the API server for Database objects, compares each one’s declared spec against real-world state, and takes action to close the gap: creating a stateful set, provisioning storage, rotating credentials. See our explainer on what a Kubernetes operator is for how this reconciliation loop is typically built. A CRD without a controller is just a schema — useful for storing structured configuration, but inert until something acts on it.

A minimal CRD in practice

A CRD is itself a Kubernetes object, applied like anything else:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.example.com
spec:
  group: example.com
  names:
    kind: Database
    plural: databases
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                engine:
                  type: string
                storageGB:
                  type: integer

Once applied, kubectl get databases works, kubectl explain database shows the schema, and users can create Database objects the same way they create Deployments — the API server validates each one against the schema before accepting it.

CRDs vs built-in resources

Built-in resourcesCustom resources (CRDs)
Defined byKubernetes coreCluster operator or vendor
SchemaFixed, versioned by Kubernetes releasesDefined per CRD, versioned independently
ReconciliationBuilt into kube-controller-managerRequires a separately deployed controller
Typical useWorkloads, networking, storage primitivesDomain-specific abstractions (databases, certificates, apps)
API surfaceSame kubectl verbs, RBAC, watch semanticsIdentical — indistinguishable to API clients

Where CRDs show up in practice

CRDs are the foundation of most Kubernetes ecosystem tooling: cert-manager represents certificates as custom resources; many database vendors ship an operator plus a CRD so kubectl apply can provision a cluster; GitOps tools often define their own CRDs to represent an application deployment as a single declarative object rather than a bundle of raw manifests. Platform teams building an internal developer platform frequently design a small set of custom resources — an Application or Environment CRD — as the abstraction developers interact with, hiding the underlying Deployments, Services, and network policies behind a simpler interface.

This pattern composes with GitOps particularly well: because custom resources are just YAML stored declaratively, a Git-based deployment pipeline can manage them with the same workflow it uses for native Kubernetes objects, with no special-casing required.

Versioning and validation matter more than they look

Because a CRD’s schema is part of an application’s API surface, changing it is a compatibility decision, not just an implementation detail. Kubernetes supports multiple versions of the same custom resource simultaneously — v1alpha1, v1beta1, v1, and so on — with one designated as the storage version and, for changes that aren’t purely additive, an optional conversion webhook that translates objects between versions on the fly. Skipping this and just editing a CRD’s schema in place is how a change that looks harmless in a pull request ends up rejecting objects that existing automation was creating just fine the day before.

The openAPIV3Schema block also does real work beyond documentation: it’s enforced by the API server at admission time, so a malformed custom resource is rejected before it’s ever persisted or seen by a controller, the same guarantee kubectl users get for free with a Deployment’s schema. Skipping schema validation — or making every field permissively typed as a string — trades that safety net for short-term convenience, and tends to push the resulting bugs downstream into the controller instead, where they’re harder to diagnose. Combined with RBAC rules scoped to the custom resource type, a well-defined CRD gives a platform team the same access-control and validation guarantees for a homegrown abstraction that Kubernetes provides natively for Pods and Services.

The takeaway

A CRD extends the Kubernetes API to understand a new kind of object, giving it the same schema validation, versioning, and kubectl ergonomics as built-in resources like Deployments. By itself, a CRD is just a shape for data — the actual behavior comes from a controller or operator watching for those objects and reconciling the cluster to match. Together, the pair is how nearly every Kubernetes extension, from certificate management to database provisioning, is built.

Chisato Chisato · · 4 min read

Kubernetes Init Containers Explained

Init containers run to completion before a pod's main containers start, making them the standard way to handle setup steps and startup ordering.

#Kubernetes #DevOps #Cloud
Chisato Chisato · · 4 min read

Helm vs Kustomize for Kubernetes Config

Helm templates and packages Kubernetes manifests with a templating language; Kustomize patches plain YAML declaratively, with no templates at all.

#Kubernetes #DevOps #Cloud