Kubernetes runs your containers, but you talk to it through YAML. Every pod, deployment, service, and config map is described by a YAML manifest that tells Kubernetes what you want. If you are new to Kubernetes, these manifests can look intimidating — but the structure is actually consistent and predictable once you understand the pattern.

This guide walks through the core resource types you need to know: Pods, Deployments, and Services. You will learn the YAML structure, what each field does, and common patterns for production use. If YAML syntax itself is new to you, our YAML vs JSON vs TOML guide is a good primer before diving in.

The Universal Structure

Every Kubernetes resource follows the same top-level structure:

apiVersion: v1          # API group and version
kind: Pod               # Type of resource
metadata:               # Name, namespace, labels
  name: my-app
  labels:
    app: my-app
spec:                   # The desired state
  # Resource-specific configuration
  • apiVersion: Tells Kubernetes which API to use. Core resources use v1. Deployments use apps/v1.
  • kind: The resource type. Kubernetes uses this with apiVersion to determine how to process the manifest.
  • metadata: Identification information — name, namespace, labels, and annotations.
  • spec: The actual configuration. This is where the resource-specific details go.

Pods: The Smallest Unit

A Pod is the smallest deployable unit in Kubernetes. It wraps one or more containers that share the same network namespace and storage volumes.

apiVersion: v1
kind: Pod
metadata:
  name: web-server
  labels:
    app: web-server
    environment: development
spec:
  containers:
    - name: nginx
      image: nginx:1.25
      ports:
        - containerPort: 80
      resources:
        requests:
          memory: "64Mi"
          cpu: "100m"
        limits:
          memory: "128Mi"
          cpu: "250m"
      readinessProbe:
        httpGet:
          path: /
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 10

Key Pod Fields

Field Purpose
containers[].name Identifier for the container within the pod
containers[].image Docker image and tag to run
containers[].ports Ports the container exposes
containers[].resources CPU and memory requests/limits
containers[].readinessProbe Check if the container is ready to serve traffic
containers[].livenessProbe Check if the container is still alive
containers[].env Environment variables
containers[].volumeMounts Where to mount volumes inside the container

Why You Rarely Create Pods Directly

In practice, you almost never create bare Pods. If a Pod crashes, nothing restarts it. If the node it runs on fails, the Pod is gone. Instead, you use higher-level controllers like Deployments that manage Pods for you.

Deployments: Managing Pod Replicas

A Deployment manages a set of identical Pods. It handles scaling, rolling updates, and self-healing. When you tell a Deployment you want three replicas, it makes sure three Pods are always running.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web
          image: my-registry/web-app:1.2.0
          ports:
            - containerPort: 8080
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
            - name: LOG_LEVEL
              value: "info"
          resources:
            requests:
              memory: "128Mi"
              cpu: "200m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 15

Understanding the Deployment Structure

The Deployment has three nested layers:

  1. Deployment spec (spec.replicas, spec.selector, spec.strategy) — controls how many Pods and how to update them
  2. Pod template (spec.template) — the blueprint for each Pod the Deployment creates
  3. Container spec (spec.template.spec.containers) — the actual container configuration

Labels and Selectors

Labels are key-value pairs attached to resources. Selectors use labels to identify which resources to manage.

spec:
  selector:
    matchLabels:
      app: web-app    # "I manage Pods with this label"
  template:
    metadata:
      labels:
        app: web-app  # "Give new Pods this label"

The selector must match the template labels. This is how the Deployment knows which Pods belong to it. If they do not match, Kubernetes rejects the manifest.

Update Strategies

The strategy field controls how Deployments roll out changes:

  • RollingUpdate (default): Gradually replaces old Pods with new ones. maxSurge controls how many extra Pods can exist during the update. maxUnavailable controls how many Pods can be down.
  • Recreate: Kills all existing Pods before creating new ones. Use this when you cannot have two versions running simultaneously (e.g., database migrations).

You can generate Kubernetes YAML quickly using our Kubernetes YAML generator instead of writing it from scratch every time.

Services: Exposing Your Pods

Pods get IP addresses, but those addresses change every time a Pod is recreated. A Service provides a stable network endpoint that routes traffic to the right Pods using label selectors.

ClusterIP Service (Default)

Exposes the service on an internal cluster IP. Only reachable from within the cluster.

apiVersion: v1
kind: Service
metadata:
  name: web-app-service
spec:
  type: ClusterIP
  selector:
    app: web-app      # Routes to Pods with this label
  ports:
    - protocol: TCP
      port: 80         # Port the service listens on
      targetPort: 8080 # Port on the container

Other Pods in the cluster can reach this service at web-app-service:80 or web-app-service.default.svc.cluster.local:80.

NodePort Service

Exposes the service on a static port on every node. Reachable from outside the cluster via <NodeIP>:<NodePort>.

apiVersion: v1
kind: Service
metadata:
  name: web-app-nodeport
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
      nodePort: 30080   # External port (30000-32767)

LoadBalancer Service

Creates an external load balancer (on cloud providers) that routes traffic to your Pods.

apiVersion: v1
kind: Service
metadata:
  name: web-app-lb
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

Choosing a Service Type

Type Use Case
ClusterIP Internal services, microservice communication
NodePort Development, on-premise deployments
LoadBalancer Production, cloud environments
ExternalName Alias for an external DNS name

Environment Variables and ConfigMaps

Hard-coding configuration in your Deployment is a bad idea. Use ConfigMaps for non-sensitive configuration and Secrets for sensitive data.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  FEATURE_FLAGS: |
    {
      "new_ui": true,
      "beta_api": false
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  # ...
  template:
    spec:
      containers:
        - name: web
          image: my-registry/web-app:1.2.0
          envFrom:
            - configMapRef:
                name: app-config

The --- separator lets you define multiple resources in one file.

Common Patterns

Multi-Container Pod (Sidecar)

spec:
  containers:
    - name: app
      image: my-app:1.0
      ports:
        - containerPort: 8080
    - name: log-shipper
      image: fluentd:latest
      volumeMounts:
        - name: logs
          mountPath: /var/log/app
  volumes:
    - name: logs
      emptyDir: {}

Init Containers

Run setup tasks before the main container starts:

spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command: ['sh', '-c', 'until nc -z postgres-service 5432; do sleep 2; done']
  containers:
    - name: app
      image: my-app:1.0

Resource Quotas

Always set resource requests and limits. Without them, a single Pod can consume all the resources on a node.

resources:
  requests:       # Guaranteed minimum
    memory: "128Mi"
    cpu: "200m"   # 200 millicores = 0.2 CPU cores
  limits:         # Maximum allowed
    memory: "256Mi"
    cpu: "500m"

Validation and Debugging

Before applying manifests, validate them:

# Dry run to check for errors
kubectl apply -f deployment.yaml --dry-run=client

# Validate against the cluster's API
kubectl apply -f deployment.yaml --dry-run=server

# Check the status after applying
kubectl get pods -l app=web-app
kubectl describe pod <pod-name>
kubectl logs <pod-name>

When converting configuration between formats during your Kubernetes work, the YAML to JSON converter is handy for transforming manifests or piping them through tools that expect JSON input.

Essential kubectl Commands

Command Purpose
kubectl apply -f file.yaml Create or update resources
kubectl get pods List pods
kubectl describe pod <name> Detailed pod info
kubectl logs <pod> -f Stream pod logs
kubectl exec -it <pod> -- /bin/sh Shell into a pod
kubectl delete -f file.yaml Delete resources
kubectl rollout status deployment/<name> Watch rollout progress
kubectl rollout undo deployment/<name> Roll back to previous version

Summary

Kubernetes YAML follows a consistent structure across all resource types. Pods define what containers to run, Deployments manage replicas and updates, and Services provide stable network endpoints. Labels connect everything together through selectors. Start with these three resource types, add ConfigMaps and Secrets for configuration, and you have the foundation for running production workloads on Kubernetes.