Declarative Object Configs
Manage resources with YAML and a reviewable change workflow.
Declarative config means you describe the desired state in files and manage everything through kubectl apply. It keeps changes reviewable, repeatable, and safer than ad-hoc imperative commands.
This quick start expands on the workflow with diff/preview, labels, patching, and GitOps-style practices.
Why it works
- Changes are reviewable in git.
- Rollbacks are simple by reapplying history.
- Reapplying the same file is safe and idempotent.
Multi-object example
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: nginx:1.25
Common flow
kubectl apply -f app.yaml
kubectl diff -f app.yaml
kubectl delete -f app.yaml
Order of operations
Start with namespaces, then RBAC, then workloads and services. This avoids dependency errors when applying multiple resources.
Labels and selectors
Use consistent labels so Services, Deployments, and monitors can discover Pods:
metadata:
labels:
app: api
tier: backend
Server-side apply
Server-side apply makes field ownership explicit and helps avoid conflicts:
kubectl apply --server-side -f app.yaml
If you see conflicts, use field managers and avoid multiple systems writing the same fields.
Field ownership and managers
Server-side apply tracks which tool owns which fields. If you use multiple tools (for example, kubectl and a GitOps controller), define a clear owner for each field to avoid fights.
You can specify a field manager explicitly:
kubectl apply --server-side --field-manager=dev-cli -f app.yaml
Diff and preview
Use kubectl diff before applying to inspect changes. For CI, you can run:
kubectl diff -f app.yaml || true
Apply ordering by files
If you split manifests across files, consider a predictable naming scheme (e.g., 00-namespace.yaml, 10-rbac.yaml, 20-app.yaml) so CI and operators apply them in the correct order.
Patch vs apply
Use kubectl apply for full desired state. Use kubectl patch for small, targeted edits:
kubectl patch deploy api -p '{"spec":{"replicas":3}}'
In production, prefer commit + apply so the desired state stays in git.
Apply with prune
If you want to delete resources removed from your manifests, use prune with labels:
kubectl apply -f ./manifests -l app=api --prune
This keeps the cluster aligned with your repo.
Namespaces and resource scope
Remember that most resources are namespace-scoped. If you apply a manifest without a namespace set, it goes to the default namespace. Make namespaces explicit in your YAML or use -n when applying.
Dry-run
Validate changes without applying:
kubectl apply -f app.yaml --dry-run=server
Validation and linting
You can validate schemas with tools like kubeconform or kubectl apply --dry-run=server in CI to catch errors early. Even basic validation prevents broken manifests from reaching production.
Kustomize basics
Kustomize is built into kubectl and helps manage environments:
kubectl apply -k overlays/prod
Use it to manage image tags and environment-specific settings without copying YAML.
GitOps workflow
In GitOps, git is the source of truth and a controller applies changes automatically. This reduces manual drift and ensures every change is reviewed.
Even without a GitOps controller, you can follow the same idea by enforcing reviews and applying only from the main branch.
Example Kustomize overlay
A minimal structure:
# kustomization.yaml
resources:
- ../../base
images:
- name: nginx
newTag: "1.26"
Apply it with:
kubectl apply -k overlays/prod
Immutable fields
Some fields cannot be updated (like selectors). If you need to change them, you must recreate the resource. Plan selectors carefully from day one.
Naming and directory structure
Organize manifests by app or environment. A common structure is:
base/for shared resources.overlays/dev,overlays/prodfor environment overrides.
This keeps changes clear and reduces duplication.
Promotion workflow
Promote changes by merging to main, then applying from a CI pipeline or GitOps controller. This ensures the same change that was reviewed is what reaches production.
Drift and reconciliation
If manual changes happen, kubectl apply will bring resources back to the desired state. This is why declarative config pairs well with GitOps controllers.
Secrets and config separation
Keep secrets out of the main manifests. Use external secret managers or separate secret manifests with restricted access. This reduces the risk of accidental leaks during reviews. Never paste secrets into issue trackers or chat logs.
Practical tips
- Use
kubectl diffin CI to review changes. - Keep manifests small and focused.
- Version your manifests with git tags to make rollback easy.
- Use consistent indentation to avoid formatting noise.
- Keep configs modular so teams can own their components.
Change review checklist
- Are labels and selectors consistent?
- Are resource requests set?
- Are secrets handled separately?
- Is a rollback path documented?
Policy and guardrails
Consider using admission controllers (like Gatekeeper or Kyverno) to enforce naming conventions, label requirements, and security constraints. This prevents invalid configs from reaching the cluster.
Ownership and handoff
If multiple teams share a repo, document ownership of each folder so reviews route to the right people. Clear ownership reduces merge conflicts and accidental overrides.
Common issues
- Apply conflicts: multiple tools manage the same fields.
- Missing namespace: resource applied before namespace exists.
- Immutable fields: selectors or service types cannot be changed.
Diff hygiene
Normalize YAML formatting and key order to keep diffs clean. This makes reviews faster and reduces accidental noise.
State of the world vs desired state
Declarative workflows assume the manifests describe the source of truth. Avoid “fixing” production by hand; instead, change the manifest and reapply. This keeps your cluster reproducible.
Split configs by concern
Keep RBAC, config, and workloads in separate files. This makes reviews clearer and reduces the risk of accidental cross-cutting changes.
Auditing changes
Keep a changelog in git or use PR descriptions to capture the intent of each change. During incidents, this context saves time.
Consistency checks
Use pre-commit hooks or CI checks to enforce basic schema and formatting rules across the repo. Consistent validation reduces runtime surprises.
Debug workflow
kubectl get -f app.yaml
kubectl describe -f app.yaml
kubectl diff -f app.yaml
Apply from directory
You can apply a directory of manifests:
kubectl apply -f ./manifests
Combine with labels to target a subset in larger repos.
What this looks like in real life
Declarative config sounds abstract until you hit the first messy rollout.
The usual story goes like this: you change a value, run kubectl apply, and now half your Pods behave differently. At that point the most valuable thing isn’t another one-off command—it’s having a clean source of truth.
So the practical habit is boring but powerful: keep manifests in version control, apply from a directory, and treat diffs as part of the release.
When something feels off, start with evidence instead of guesses:
kubectl get nodes
kubectl get pods -A
kubectl get events -A --sort-by=.lastTimestamp
kubectl describe -f app.yaml
kubectl diff -f app.yaml
Two common ‘gotchas’ that show up in teams new to declarative workflows:
- You edited the YAML, but applied it to the wrong namespace/context.
- Labels/selectors drifted, so Services or controllers no longer match what you think they match.
If you only do one ‘production’ thing: write down the rollback path (what commit/tag do we go back to, and how do we apply it). It sounds obvious, but it’s the difference between a calm rollback and a panic rollback.