What is a Custom Resource Definition (CRD), and why would you create one?

7 minadvancedcrdcustom-resourcesextensibility

Quick Answer

A CRD lets you define an entirely new kind of object in the Kubernetes API — with your own schema, your own `kind`, managed exactly like any built-in object (`kubectl get`, `kubectl apply`, stored in etcd, subject to RBAC) — without modifying Kubernetes itself. You'd create one to represent a concept that's meaningful to your application or platform but has no built-in Kubernetes equivalent (a "Database" object representing a managed database instance, a "CronBackup" representing a scheduled backup policy), giving that concept a first-class, declarative API rather than hand-rolling it as ad-hoc ConfigMap conventions or external tooling state.

Detailed Answer

Defining a CRD

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.example.com
spec:
  group: example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                engine:
                  type: string
                  enum: ["postgres", "mysql"]
                storageSize:
                  type: string
                replicas:
                  type: integer
  scope: Namespaced
  names:
    plural: databases
    singular: database
    kind: Database

Once this CRD is applied, Database becomes a genuinely new, first-class Kubernetes object type — the API server now accepts, validates (against the declared schema), and stores Database objects exactly as it does built-in ones like Deployments or Services.

Creating an instance of your new custom resource

apiVersion: example.com/v1
kind: Database
metadata:
  name: my-app-db
spec:
  engine: postgres
  storageSize: "50Gi"
  replicas: 3
kubectl get databases
kubectl describe database my-app-db
kubectl apply -f my-database.yaml

This works with kubectl, RBAC, kubectl apply/GitOps workflows, and any generic Kubernetes tooling — all identically to how they work with built-in objects, because a CRD-defined custom resource genuinely is just another object type to the API server, with no special-casing needed.

Why create one, rather than using a ConfigMap or an external system

  • First-class API semantics — a CRD gets schema validation, versioning, RBAC scoping, and kubectl support natively; representing the same concept as a cleverly-structured ConfigMap gets none of this for free, and requires custom tooling to validate/interpret its contents.
  • A natural fit for the reconciliation model — a CRD paired with a custom controller (an Operator — see that question) lets you build genuinely new, declarative "desired state, reconciled automatically" behavior for concepts specific to your domain, using the exact same pattern that powers Deployments and every other built-in controller.
  • Discoverability and consistency — anyone familiar with kubectl already knows how to interact with your custom resource; there's no separate CLI or API convention to learn.

A CRD alone is just a schema — it does nothing by itself

Critically, defining a CRD and creating Database objects does not, on its own, cause any actual database to be provisioned anywhere — a CRD only teaches the API server to accept, validate, and store objects of that shape. Making something actually happen in response to a Database object being created (actually provisioning a real database instance) requires a controller watching for these objects and reconciling real-world state to match them — this is exactly the role an Operator plays (see that question), and the combination of "CRD defines the shape" + "Operator provides the behavior" is the standard, complete pattern for meaningfully extending Kubernetes.

When creating a CRD is (and isn't) the right call

Worth the investment when you're building genuine platform/infrastructure tooling meant to be consumed declaratively by many users or teams, especially when the underlying concept benefits from Kubernetes-native reconciliation (self-healing, GitOps-compatible desired state). Often overkill for a one-off internal need that a simpler mechanism (a ConfigMap, a small external service, a script) would satisfy just as well with far less implementation effort — CRDs plus a working Operator represent real engineering investment, not a lightweight configuration trick.