Foundations configuration

YAML Manifests & Declarative Config

● Beginner ⏱ 12 min read configuration

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.

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:

yaml-examples.yaml
# 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"]
⚠️
Indentation errors are the #1 YAML mistake

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:

anatomy.yaml
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:

app.yaml
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.

Directory structure
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
k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
k8s/overlays/prod/kustomization.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've completed Chapter I — Foundations!

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.