Skip to main content
Kubernetes·8 min read

pod.yaml Breakdown — Every Field in a Kubernetes Pod Manifest Explained

A field-by-field walkthrough of a complete Kubernetes Pod manifest: apiVersion, kind, metadata, containers, resources, probes, volumes, restartPolicy, and nodeSelector — plus why you usually run a Deployment instead.

DT

DevOps Engineer & Technical Writer

The Problem

Everyone copies a Pod YAML off the internet, tweaks the image, and ships it — without knowing what half the fields do. Then a Pod gets OOMKilled, or lands on the wrong node, or restarts forever, and there's no way to reason about it because the manifest is a black box. A Pod spec is small and every field earns its place. Read one carefully, once, and you can read every manifest after it.

POD MANIFEST STRUCTURE — FIELD MAP apiVersion: v1 · kind: Pod which API group + which object type metadata name labels (selectors key off these) annotations (free-form metadata) spec containers[] image · ports · env resources (requests / limits) probes (liveness / readiness / startup) volumeMounts volumes[] configMap · emptyDir · pvc restartPolicy: Always nodeSelector: { disktype: ssd } metadata = identity spec = desired runtime behavior

Every Kubernetes object follows the same four-part shape: apiVersion, kind, metadata, and spec. A Pod is the simplest workload object, so it's the perfect place to learn that shape.

The Complete Manifest

Here is a fully annotated Pod. We'll walk through it top to bottom.

apiVersion: v1                 # core API group, version 1

kind: Pod # the object type

metadata:

name: web # unique within the namespace

labels: # key/value tags used by selectors

app: web

tier: frontend

annotations: # non-identifying metadata (tools, notes)

team: platform

spec:

containers:

- name: web # container name, unique within the pod

image: nginx:1.27 # pin a version, never use :latest in prod

ports:

- containerPort: 80 # documents the port the app listens on

env:

- name: LOG_LEVEL

value: "info"

- name: DB_PASSWORD # pull secrets from a Secret, not plain text

valueFrom:

secretKeyRef:

name: db-creds

key: password

resources:

requests: # guaranteed minimum (used for scheduling)

cpu: "100m"

memory: "128Mi"

limits: # hard ceiling (exceed memory = OOMKilled)

cpu: "500m"

memory: "256Mi"

volumeMounts:

- name: html

mountPath: /usr/share/nginx/html

livenessProbe:

httpGet: { path: /, port: 80 }

periodSeconds: 10

readinessProbe:

httpGet: { path: /, port: 80 }

periodSeconds: 5

volumes:

- name: html

configMap:

name: web-content

restartPolicy: Always # Always | OnFailure | Never

nodeSelector:

disktype: ssd # only schedule onto nodes with this label

apiVersion and kind

apiVersion tells the API server which API group and version to validate against — Pods live in the core group, so it's just v1. kind names the object type. Together they decide the schema. Get these wrong and the server rejects the manifest before anything else is read.

metadata — The Object's Identity

  • name — must be unique within the namespace. This is how you reference the Pod.
  • labels — arbitrary key/value pairs that other objects select on. Services, Deployments, and kubectl get -l app=web all rely on labels. This is the most important metadata field.
  • annotations — also key/value, but not used for selection. They hold free-form metadata: ownership, tooling config, change-cause notes.

The practical difference: you filter and target workloads by labels; you attach descriptive data with annotations.

spec.containers — What Actually Runs

image

The container image, ideally pinned to a specific tag or digest. Using :latest in production is a reliability trap — you can't tell what's running and rollbacks become guesswork.

ports

containerPort documents the port the process listens on. It's informational — it doesn't publish anything by itself — but it's good practice and some tooling reads it.

env

Environment variables. Inline literals for non-sensitive config, valueFrom.secretKeyRef for secrets, and valueFrom.configMapKeyRef for shared config. Never hard-code passwords as plain value entries.

resources — requests vs limits

This is the field people skip and regret:

FieldMeaning
<code class="inline-code">requests</code>The amount reserved for the container. The <strong>scheduler</strong> uses this to decide which node fits.
<code class="inline-code">limits</code>The hard ceiling. Exceed the <strong>memory</strong> limit and the container is <code class="inline-code">OOMKilled</code>; exceed the <strong>CPU</strong> limit and it's throttled, not killed.

Set requests so the scheduler places Pods sensibly, and set limits to protect the node from a runaway container.

volumeMounts

Mounts a volume (declared below in spec.volumes) into the container's filesystem at mountPath. The name must match a volume name.

probes

livenessProbe, readinessProbe, and startupProbe tell the kubelet whether the app is alive, ready for traffic, and finished starting. See the Pod lifecycle guide for how each one changes behavior.

spec.volumes

Volumes are declared at the Pod level and mounted into one or more containers. Common types:

  • configMap / secret — surface config or secrets as files.
  • emptyDir — scratch space that lives as long as the Pod.
  • persistentVolumeClaim — durable storage that survives Pod restarts.

restartPolicy and nodeSelector

  • restartPolicy — Always (services), OnFailure (jobs), or Never. Applies to all containers in the Pod.
  • nodeSelector — the simplest node-targeting mechanism: schedule only onto nodes carrying the given label. For richer rules use affinity, taints, and tolerations.

In Production, Prefer a Deployment

A bare Pod has no self-healing. If its node dies, the Pod is gone — nothing recreates it. In real systems you almost never apply a Pod directly. Instead you create a Deployment, which manages a ReplicaSet that keeps the desired number of Pods running, handles rolling updates, and recreates Pods after node failures.

apiVersion: apps/v1

kind: Deployment

metadata:

name: web

spec:

replicas: 3

selector:

matchLabels:

app: web

template: # this is a Pod spec — the same fields you just learned

metadata:

labels:

app: web

spec:

containers:

- name: web

image: nginx:1.27

Notice the template is just the Pod spec. Everything you learned above carries straight over — a Deployment is a manager wrapped around the same Pod definition.

# Validate a manifest without applying it

kubectl apply -f pod.yaml --dry-run=server

# See the fully-populated object (with defaults filled in)

kubectl get pod web -o yaml

Common Mistakes to Avoid

  • Using image: latest. You lose track of what's deployed and can't roll back reliably. Pin versions.
  • Omitting resource requests and limits. Without requests the scheduler can overpack a node; without a memory limit one Pod can starve the whole node.
  • Confusing labels and annotations. Selectors only match labels. Put anything you need to query into labels, not annotations.
  • Hard-coding secrets in env.value. Use secretKeyRef. Plain-text secrets end up in Git and in kubectl get -o yaml.
  • Running bare Pods in production. No controller means no self-healing. Use a Deployment (or StatefulSet/DaemonSet/Job as appropriate).

FAQ

What is the difference between resource requests and limits?

A request is the amount of CPU or memory reserved for the container and is what the scheduler uses to place the Pod on a node. A limit is the maximum the container may use — exceeding the memory limit gets the container OOMKilled, while exceeding the CPU limit only throttles it. Set requests for scheduling accuracy and limits to protect the node.

Why should I use a Deployment instead of a Pod?

A bare Pod has no controller watching it, so if its node fails the Pod simply disappears and nothing recreates it. A Deployment manages a ReplicaSet that maintains the desired replica count, performs rolling updates, and reschedules Pods after failures. The Deployment's template is just a Pod spec, so everything you know about Pods still applies.

What is the difference between labels and annotations?

Labels are key/value pairs that selectors use to find and group objects — Services and Deployments match Pods by their labels. Annotations are also key/value pairs but are ignored by selectors; they hold descriptive or tooling metadata. If you need to query or target an object by a value, it belongs in labels.

Do I need to declare containerPort for my app to receive traffic?

No. containerPort is informational and does not open or publish a port by itself — the container listens regardless. Traffic routing is handled by Services and their target ports. Declaring containerPort is still good practice because it documents intent and some tooling reads it.