Skip to main content
Before you write controller code, define the shape of the custom resource that users will provide. That shape is the spec — think of it as a concise request form that declares the desired state. For this WebApp operator, the first API version keeps the spec intentionally small with just two fields. The first field is image. It is a string that tells the Deployment which container image to run. The second field is replicas. It is an int32 (the conventional small integer type Kubernetes uses for replica counts). replicas tells the Deployment how many Pods should exist.
The image explains how to specify the number of pods to keep in Kubernetes using a "replicas" field, with a basic request form showing example fields for "image" and "replicas". Additionally, it notes that Kubernetes uses the int32 type for replica counts.
Notice what is deliberately excluded from this first-version spec. There is no service type, no custom port, no nested configuration object, and no status field — those are left out so the initial API remains focused and easy to reason about.
The image features a list titled "Deliberately Left Out – For Now" with three items: Service type, Custom port, and Nested config object. Each item is accompanied by an icon.
Even though the spec doesn’t expose these options yet, the operator will still create a Service and a ConfigMap for the application. The operator uses clear, fixed defaults for those child resources so the reconciliation loop can be validated end to end without extra user configuration.
The image shows a diagram with two labeled components: "Service" and "ConfigMap," under the heading "The Operator Fills In Clear, Fixed Defaults."
Concretely:
  • Service default: ClusterIP on port 80.
  • ConfigMap default: a simple static welcome page.
This minimal-first-version approach keeps the user API small while letting you observe the reconcile loop working across Deployment, Service, and ConfigMap. You can add more spec fields later (for example port), but start small so the first reconciler remains readable and testable.
Start with a narrow API surface. Small, opinionated defaults let you validate reconciliation behavior quickly; expand the spec only when you need to expose additional user-configurable options.
Example API sketches:
The user writes a minimal desired state and the controller creates the child objects that make that state real.
The image illustrates a flowchart of a reconciliation process, where a user writes a small desired state, a controller creates children, and child objects make the state real.
In Go, express this spec as fields on the WebAppSpec struct. The JSON struct tags determine the YAML/JSON field names users write in their manifests:
Kubebuilder markers (Go comments processed by controller-gen) can add API-server behavior such as defaults and validation. Running make manifests converts those markers into the CRD OpenAPI schema that the API server enforces.
Remember to add kubebuilder validation and default markers if you expect defaults or input validation. Without them, the API server won’t enforce constraints and invalid manifests could reach your controller.
Summary table — WebApp first-version contract: Next step: translate this design into controller and reconcile logic that instantiates the Deployment, Service, and ConfigMap. Implement the reconcile loop to:
  1. Read the WebApp instance and its spec.
  2. Construct desired child resources (Deployment, Service, ConfigMap) using the spec values and operator defaults.
  3. Create or update child resources, set owner references, and ensure labels/selector consistency.
  4. Requeue and reconcile until observed state matches desired state.
References:

Watch Video