Article 11 – Declarative Pods with YAML: Writing Your First Spec

The Friction of Command-Line Infrastructure

When starting out with containers, you often configure everything using CLI flags. You run commands like:
docker run -d --name my-app -p 80:80 -v /var/data:/data --restart=always my-image:v2

While this is quick for local testing, it is difficult to manage in production. You cannot easily store CLI histories in a Git repository, run code reviews on them, or track who changed a parameter. If you need to rebuild your environment, you have to recreate the exact CLI commands from memory.

Managing infrastructure through interactive commands is prone to error and hard to replicate.

Structuring the Kubernetes YAML Schema

Kubernetes solves this by using declarative configuration files written in YAML.

Every Kubernetes resource definition file—whether it is a Pod, a Service, a ReplicaSet, or a Deployment—must contain four mandatory top-level fields:

  1. apiVersion: Specifies the version of the Kubernetes API schema used to create the object. The API is divided into versions and groups (such as v1 or apps/v1).
  2. kind: The type of resource you want to create (e.g., Pod, Service, ReplicaSet, Deployment). This field is case-sensitive.
  3. metadata: A dictionary containing identifying data like name, namespace, labels, and annotations.
  4. labels: Key-value pairs used to identify and group resources (e.g., app: nginx). Controllers use labels to find pods.
  5. annotations: Key-value pairs used to store non-identifying metadata, such as build versions, release dates, or contact emails. Kubernetes does not query annotations to select resources.
  6. spec: Declares the desired state of the resource. The configuration details under spec differ depending on the resource kind (e.g., container images, ports, and storage volumes).

Here is a quick reference table for the apiVersion and kind pairings:

Resource TypeapiVersionkind (Case-Sensitive!)
Podv1Pod
Servicev1Service
ReplicaSetapps/v1ReplicaSet
Deploymentapps/v1Deployment

Writing and Deploying a Pod Spec

Let’s define a Pod using YAML and deploy it to the cluster. Save the following configuration as my-first-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-webserver
  labels:
    app: web
    tier: frontend
  annotations:
    owner: "kapil"
    build-id: "20260605.1"
spec:
  containers:
  - name: nginx-container
    image: nginx:1.25
    ports:
    - containerPort: 80
    env:
    - name: ENV_VAR_EXAMPLE
      value: "production"

Before you deploy, you can validate your YAML syntax and schema against the API Server using the dry-run flag:

$ kubectl apply -f my-first-pod.yaml --dry-run=client
pod/nginx-webserver created (dry run)

If it succeeds, deploy the Pod to the cluster:

$ kubectl apply -f my-first-pod.yaml
pod/nginx-webserver created

To view the running Pods in the namespace:

$ kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
nginx-webserver   1/1     Running   0          15s

If you need to inspect the status, configuration details, and lifecycle events of a Pod, use the describe command:

$ kubectl describe pod nginx-webserver

This command prints a detailed report of the Pod’s lifecycle history:

Name:             nginx-webserver
Namespace:        default
Node:             worker-node-1/192.168.49.3
Start Time:       Fri, 05 Jun 2026 08:40:00 +0530
Labels:           app=web
                  tier=frontend
Annotations:      build-id: 20260605.1
                  owner: kapil
Status:           Running
IP:               10.244.1.5
Containers:
  nginx-container:
    Container ID:   containerd://1d7b32...
    Image:          nginx:1.25
    Port:           80/TCP
    State:          Running
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  30s   default-scheduler  Successfully assigned default/nginx-webserver to worker-node-1
  Normal  Pulling    29s   kubelet            Pulling image "nginx:1.25"
  Normal  Pulled     25s   kubelet            Successfully pulled image "nginx:1.25" in 4.12s
  Normal  Created    25s   kubelet            Created container nginx-container
  Normal  Start      24s   kubelet            Started container nginx-container

The events log at the bottom is highly useful for troubleshooting. If your container fails to start because of an image download error or a configuration mistake, the exact reason will be shown here.