# Kubernetes YAML for Beginners: Pods, Deployments, Services

> Learn how to write Kubernetes YAML manifests for Pods, Deployments, and Services. Covers labels, selectors, common patterns, and practical examples.

- URL: https://www.browserutils.dev/blog/kubernetes-yaml-beginners
- Published: 2026-09-14
- Updated: 2026-07-02

---

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](/blog/yaml-vs-json-vs-toml) is a good primer before diving in.

## The Universal Structure

Every Kubernetes resource follows the same top-level structure:

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

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

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

```yaml
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](/tools/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.

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

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

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

```yaml
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)

```yaml
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:

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

```yaml
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:

```bash
# 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](/tools/yaml-to-json) 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.