YAML Manifests & Declarative Config
Every Kubernetes resource is described as a
manifest — a YAML (or JSON) document that declares
the desired state of an object. When you run
kubectl apply -f deployment.yaml, you are telling the
cluster what you want; Kubernetes figures out how to make it happen.
This declarative model is the cornerstone of reliable, reproducible
infrastructure.
Based on kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/.
Declarative vs. Imperative
Kubernetes supports two styles of interaction:
| Style | How | Pros | Cons |
|---|---|---|---|
| Imperative |
kubectl create, kubectl run,
kubectl expose
|
Quick for one-offs and learning | Not reproducible; hard to track in Git; can't diff changes |
| Declarative | kubectl apply -f manifest.yaml |
Reproducible; GitOps-friendly; diffable; auditable | Slightly more verbose upfront |
The golden rule:
always use declarative manifests in production. Use
imperative commands only for exploration or to generate initial
manifest boilerplate with --dry-run=client -o yaml.
YAML Primer
YAML is a superset of JSON that uses indentation (spaces, never tabs) to express structure. The rules you need for Kubernetes manifests:
# Mapping (key: value)
name: nginx-pod
replicas: 3
enabled: true
# Sequence (list)
ports:
- 80
- 443
# Nested mapping
metadata:
name: my-pod
namespace: default
labels:
app: nginx
# Multi-line string (literal block — preserves newlines)
command: |
#!/bin/sh
echo "hello"
exec nginx -g "daemon off;"
# Multi-line string (folded — newlines become spaces)
description: >
This is a very long description that
wraps across multiple lines.
# Inline list
args: ["--config", "/etc/app/config.yaml"]
Kubernetes YAML uses 2-space indentation by
convention. Mixing tabs and spaces, or inconsistent indentation
depth, causes cryptic
error parsing manifest messages. Use an editor with
YAML support (VS Code + YAML extension) and validate with
kubectl apply --dry-run=client before committing.
The Four Top-level Fields
Every Kubernetes manifest has exactly four top-level fields:
apiVersion: apps/v1 # 1. API group/version
kind: Deployment # 2. Resource type
metadata: # 3. Identity and labels
name: payments-api
namespace: production
labels:
app: payments-api
version: "2.4.1"
spec: # 4. Desired state (resource-specific)
replicas: 3
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
containers:
- name: api
image: payments-api:2.4.1
ports:
- containerPort: 8080
| Field | Purpose | Example |
|---|---|---|
apiVersion |
Which API group and version the resource belongs to |
v1, apps/v1,
networking.k8s.io/v1
|
kind |
The type of resource |
Pod, Deployment,
Service, Namespace
|
metadata |
Identifying information: name, namespace, labels, annotations | |
spec |
The desired state — fields vary entirely by resource type |
Finding the correct apiVersion
Use kubectl api-resources to list every resource type
along with its API group, then combine them:
kubectl api-resources | grep -i deployment
# APIGROUP = apps → apiVersion: apps/v1
kubectl api-resources | grep -i ingress
# APIGROUP = networking.k8s.io → apiVersion: networking.k8s.io/v1
kubectl api-resources | grep -i pod
# APIGROUP = (empty) → core group → apiVersion: v1
Multi-document Files
YAML files can contain multiple documents separated by
---. Use this to bundle related resources together in one
file:
apiVersion: v1
kind: Namespace
metadata:
name: payments
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: payments
spec:
replicas: 2
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
containers:
- name: api
image: payments-api:2.4.1
---
apiVersion: v1
kind: Service
metadata:
name: payments-api
namespace: payments
spec:
selector:
app: payments-api
ports:
- port: 80
targetPort: 8080
Apply all three with a single command:
kubectl apply -f app.yaml
Dry Run & Diff
Before applying changes to a live cluster, validate them first:
# client-side validation (no cluster call)
kubectl apply -f deployment.yaml --dry-run=client
# server-side validation (cluster validates, doesn't apply)
kubectl apply -f deployment.yaml --dry-run=server
# diff current live state against the new manifest
kubectl diff -f deployment.yaml
kubectl diff shows a unified diff of what would change —
indispensable before rolling out updates in production.
Kustomize
Kustomize is built into kubectl and allows you to customise manifests for different environments (dev, staging, prod) without duplicating YAML. It works through overlays that patch a base set of manifests.
k8s/
base/
deployment.yaml
service.yaml
kustomization.yaml # references base resources
overlays/
dev/
kustomization.yaml # patches: replicas=1, image tag=dev
prod/
kustomization.yaml # patches: replicas=5, image tag=v2.4.1
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
patches:
- patch: |-
- op: replace
path: /spec/replicas
value: 5
target:
kind: Deployment
name: payments-api
images:
- name: payments-api
newTag: "2.4.1"
# preview the rendered output for prod
kubectl kustomize k8s/overlays/prod
# apply the prod overlay
kubectl apply -k k8s/overlays/prod
Best Practices
| Practice | Why |
|---|---|
Always set metadata.namespace explicitly |
Avoids accidentally deploying into the wrong namespace |
Pin image tags — never use :latest |
Reproducible deploys; avoids silent breaking changes |
| Set resource requests and limits on every container | Enables proper scheduling and prevents runaway containers |
| Store manifests in Git |
Audit trail, rollback via git revert, code review
for infra changes
|
Use kubectl diff before
kubectl apply in prod
|
Catch unintended changes before they reach production |
Validate with --dry-run=server in CI |
Catches schema errors and admission webhook rejections early |
Use --- separators to bundle related resources
|
Single apply deploys the full stack atomically |
You now understand what Kubernetes is, how its architecture works, what pods are, how to organise resources with namespaces and labels, how to use kubectl, how to run a local cluster, and how to write declarative manifests. Chapter II covers Workloads: Deployments, StatefulSets, DaemonSets, Jobs, and autoscaling — coming soon.