Kubebuilder API Design (Go)
Design Kubernetes APIs with Kubebuilder in Go (primarily for Kubernetes operators): CRD type definitions, schema markers, status/conditions, and the core scaffold/regenerate workflow.
Default scope: API design + CRD generation. Do not implement controller/webhook business logic unless explicitly requested.
Tooling assumptions (what needs to exist for generation)
This skill can help with pure API design (Go types + markers) without running anything, but to scaffold and to regenerate CRDs/manifests the user’s environment/project typically needs:
gotoolchain (matching the project’sgo.modrequirements)kubebuilderCLI (or Operator SDK if they wrap Kubebuilder)make(Kubebuilder projects drive generation viaMakefile)
Notes:
controller-genis usually installed automatically by the project’s Makefile target(s) (pinned version), so it’s not always a separate prerequisite.- Cluster tooling (
kubectl,kind, etc.) is only needed if they want to run/test against a cluster; keep this out of scope unless explicitly requested.
Relationship to k8s-crd-design-review
Treat the generated CRD YAML as the *compiled API contract*. After shaping Go types + markers and running generation, run a design review on the generated CRD YAML (or diff) using ../k8s-crd-design-review/SKILL.md.
SSA / GitOps note: list semantics are part of the API contract.
- Use list semantics markers (
+listType=set|map,+listMapKey=...) early so generated CRDs behave well under SSA and produce stable diffs. - Deeper guidance lives in
../k8s-crd-design-review/references/list-semantics-gitops-ssa.md.
Versioning / multi-version CRDs (pointer)
If the user is designing a multi-version API (e.g., v1alpha1 → v1beta1 → v1) or needs conversion webhooks:
- Follow Kubebuilder’s multiversion tutorial for the mechanics:
book.kubebuilder.io/multiversion-tutorial/tutorial.html. - Keep this skill focused on “Go types + markers + generation”. For compatibility review and migration planning, hand off to
../k8s-crd-design-review/SKILL.md(especially../k8s-crd-design-review/references/versioning-and-migrations.md).
Designing relations (object references) in Spec/Status
When a CRD needs to point at another Kubernetes object (or one of its fields/keys), treat “references” as API design, not a convenience import.
Prefer well-scoped, API-owned reference types (avoid generic core types)
Avoid embedding generic Kubernetes reference structs like corev1.LocalObjectReference or corev1.ObjectReference in *new* APIs.
Kubernetes upstream explicitly discourages new uses of corev1.LocalObjectReference and corev1.ObjectReference because it is underspecified and hard to validate/document per usage:
- Key takeaway: “Instead of using these generic types, create a locally provided and used type that is well-focused on your reference.”
This guidance applies equally to other overly-generic reference types you may find in older APIs (they tend to have unclear semantics, inconsistent validation, and awkward defaults).
Pattern: define a dedicated <Thing>Ref type per relationship
Define a small, *purpose-built* struct for each relationship, tailored to your CRD:
- Make required fields actually required (don’t mirror upstream backward-compatibility tricks like
omitempty+ default empty string) - Encode the relationship’s real constraints in schema (name format, allowed kinds, namespace rules)
- Add per-field documentation that matches the domain (so
kubectl explainis useful)
Example (same-namespace reference by name):
// ConfigMapRef identifies a ConfigMap in the same namespace.
// The referenced ConfigMap must exist before this resource becomes Ready.
type ConfigMapRef struct {
// Name is the name of the referenced ConfigMap.
// +kubebuilder:validation:MinLength=1
// Consider adding a stricter pattern if you want to enforce DNS-1123 label.
Name string `json:"name"`
}Example (namespace + name, if cross-namespace is allowed by design):
// SecretRef identifies a Secret.
// If Namespace is omitted, it defaults to the resource namespace.
type SecretRef struct {
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
// +optional
// +kubebuilder:validation:MinLength=1
Namespace string `json:"namespace,omitempty"`
}Decide (and document) the reference semantics up front
For each relation, explicitly choose and document:
- Scope: same-namespace only vs cross-namespace (cross-namespace typically needs extra authorization/guardrails)
- Identity: name only vs name+namespace vs (rarely) UID/resourceVersion (most APIs should stick to name/namespace)
- Allowed targets: fixed Kind (recommended) vs “one-of” kinds (if so, model it explicitly)
- Lifecycle behavior: what happens if the target is missing, renamed, deleted, or recreated
- Status mirroring: if you surface resolved details (e.g., observed UID), put them in
StatusnotSpec
If the user asks for “a reference to an arbitrary object”, push back and narrow it: require a specific Kind or a small, explicit set of Kinds, then reflect that in the API type.
Workflow
Step 0 — Gather inputs (ask only what’s needed)
- API identity: Group, Version, Kind, Plural, Scope (Namespaced vs Cluster)
- Desired Spec fields (including which are required) and any immutability expectations
- Status needs: conditions? observedGeneration? summary fields?
- Any constraints: enum values, ranges, patterns, max items, uniqueness, references to other objects
When the user mentions relations/references, capture:
- target kind(s) and whether cross-namespace is allowed
- whether the reference is required
- whether they need to reference a whole object or a sub-field (e.g., secretKeyRef)
- expected behavior when the target does not exist or changes
If the user already has YAML for the CRD, treat it as the contract and map it into Go types + markers.
Step 1 — Draft the Go API types
Create:
type <Kind>Spec struct {...}type <Kind>Status struct {...}type <Kind> struct {metav1.TypeMeta; metav1.ObjectMeta; Spec; Status}type <Kind>List struct {metav1.TypeMeta; metav1.ListMeta; Items []<Kind>}
Rules of thumb:
- Use pointers for optional scalars and optional structs to preserve “unset vs zero”.
- Prefer Kubernetes-native types when relevant (
metav1.Time,resource.Quantity,intstr.IntOrString). - Keep
Statusseparate fromSpec. IncludeObservedGenerationandConditionswhen useful. - For relations, prefer API-owned
<Thing>Refstructs over embedding generic Kubernetes reference types (see “Designing relations”).
Read ./references/go-type-patterns.md for common Go/Kubernetes type choices.
Step 2 — Add Kubebuilder markers (validation, defaults, printing)
Add markers to match the desired API contract:
- Root + subresources:
- +kubebuilder:object:root=true - +kubebuilder:subresource:status
- Validation + defaults on fields (min/max, pattern, enum, length, items, etc.)
- List semantics for SSA and correctness (
listType=set|map,listMapKey=...) - Printer columns that reflect the most important status/spec at a glance
Read ./references/kubebuilder-markers.md.
For a compact “what marker do I need?” cheat-sheet (root markers, field validation/defaults, list semantics, and printer columns), see ./references/api_reference.md.
Step 3 — Shape Status + Conditions
Prefer []metav1.Condition unless you have a strong reason to roll your own condition type. Include helper summary fields only if they serve UX.
Read ./references/status-and-conditions.md (Kubebuilder/Go implementation appendix). For canonical conceptual semantics and review heuristics, defer to k8s-crd-design-review/references/conditions-and-status.md.
Step 3.5 — Kubebuilder workflow essentials (scaffolding + generation)
When the user needs help with Kubebuilder scaffolding and regeneration steps (but not controller/webhook business logic), follow the short checklist in ./references/kubebuilder-workflow-essentials.md.
Step 4 — Provide a “generate + verify” checklist (don’t implement)
Give the user the standard Kubebuilder steps to regenerate CRDs and verify the schema, without writing controller logic:
- Run
make generate/make manifests - Inspect the generated CRD YAML and confirm:
- required fields match expectations - defaults appear where intended - list semantics and map keys are correct - printer columns render
Then (recommended): run a contract review pass against the generated CRD YAML (or diff) using ../k8s-crd-design-review/SKILL.md, focusing on compatibility/migration impact, lifecycle/status semantics, and SSA/GitOps ergonomics.
When the user needs more API-shape guidance, point them to the offical long read of the Kubernetes API conventions.
Output format
When delivering code, output a single Go snippet per file:
- package + imports
- markers
Spec,Status- root object + list
- any type aliases and nested structs
Use concise comments. Put rationale (why a pointer, why listType map, etc.) in short bullet notes after the code.
Resources
Kubebuilder book (upstream docs mirror in this repo)
If you need canonical Kubebuilder explanations (beyond this skill’s cheat-sheets), prefer these pages:
- Markers overview + syntax:
book.kubebuilder.io/reference/markers.html - CRD generation + subresources + storageversion:
book.kubebuilder.io/reference/generating-crd.html - Operator API example uses
Conditionswith map-like list semantics:book.kubebuilder.io/getting-started.html - “Designing an API” (types like
resource.Quantity,metav1.Time):book.kubebuilder.io/cronjob-tutorial/api-design.html
references/
Load these only when needed (to keep context small):
./references/go-type-patterns.md./references/kubebuilder-markers.md./references/api_reference.md./references/status-and-conditions.md(Kubebuilder/Go implementation appendix; conceptual semantics live in../k8s-crd-design-review/references/conditions-and-status.md)./references/kubebuilder-workflow-essentials.md
This skill currently only bundles references/. Add scripts/ or assets/ later if you want automation helpers or boilerplate templates.