- API versioning using
servedandstorageflags - The
statussubresource to separate controller-managed state from user intent - Additional printer columns to improve
kubectl getoutput - The
scalesubresource forkubectl scaleand Horizontal Pod Autoscaler (HPA) integration
Example: costly lack of versioning
A team created aDatabase CRD with a single v1 version and no status subresource. Six months later they added a new required backup field to the schema. Because existing resources were stored without it, applying the change broke those resources. The team had to:
- Create a new CRD
database.v2 - Manually migrate resources
- Update callers across the codebase

Common problems when design patterns are skipped
- No versioning strategy: adding or changing required fields can invalidate existing resources and break clients.
- No
statusseparation: users or kubectl can accidentally overwrite controller-managedstatusfields, creating race conditions. - Poor
kubectlUX: defaultkubectl getshows onlyNAMEandAGE— forcing extra commands to check health, size, or replicas.
API versioning
Kubernetes versioning commonly follows:v1alpha1 (experimental) → v1beta1 (pre-release) → v1 (stable). Plan versioning from day one even if you only ship a single version initially.
Two flags control each CRD version’s runtime behavior:
Typical evolution:
- Start with
v1alpha1(served: true,storage: true). - Add
v1beta1asserved: true, setv1beta1tostorage: true, and switchv1alpha1tostorage: false. The API server migrates stored resources. - Promote to
v1once stable and repeat the migration process.
deprecated and deprecationWarning to notify users when a version will be removed:
- Kubernetes CRD versioning and conversion docs: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#conversion

Status subresource
Without a dedicatedstatus subresource, a single PUT can overwrite both spec and status. This allows users or controllers to inadvertently clobber controller-managed state.
When you enable status, the API server exposes two distinct endpoints:
- Update
speconly:PUT /apis/<group>/<version>/namespaces/<ns>/<plural>/<name>
- Update
statusonly:PUT /apis/<group>/<version>/namespaces/<ns>/<plural>/<name>/status
spec is user intent and status is controller-observed state.
Enable status simply in the CRD version:

Always enable the
status subresource for controller-managed CRs. Without it, controllers and users can overwrite each other’s fields, causing hard-to-debug race conditions.Observed generation pattern
Usemetadata.generation and status.observedGeneration to communicate reconciliation progress.
metadata.generationincrements when thespecchanges.- The controller sets
status.observedGenerationto the lastgenerationit has reconciled.
status snippet:
spec is what you want; status is what you have. Controllers reconcile the difference.
Additional printer columns
Printer columns give operators a quick glance at important fields inkubectl get output (e.g., size, phase, replicas).
Add them to the CRD versions entry. Each column requires name, type, and a jsonPath referencing resource fields (you can use .spec and .status).
Example:

Scale subresource
If your CR exposes replicas, enable thescale subresource so kubectl scale works and the HPA can target your CR.
The scale mapping wires your CR fields into the standard kubernetes Scale API:
specReplicasPath: where users set the desired count.statusReplicasPath: where the controller reports current count.labelSelectorPath(optional): HPA uses this to locate pods if needed.
- HPA documentation: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
Production-ready CRD example
A consolidated CRD that applies versioning,status, printer columns, and schema validation:
Design checklist
Use this checklist before promoting a CRD to production:Plan for versioning and status separation from day one. These patterns prevent expensive migrations and help CRDs behave like native Kubernetes APIs.
Key takeaways
- Version your APIs early and make upgrades deliberate.
- Enable the
statussubresource to prevent accidental overwrites and enable safe controller updates. - Improve operator experience with
additionalPrinterColumns. - Enable
scalefor resources that manage replicas sokubectl scaleand HPA work naturally. - A well-designed CRD should feel and behave like a native Kubernetes API—discoverable, safe, and operable at scale.
- Kubernetes CRD docs: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
- CRD Conversion webhooks: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#webhook-conversion