Pods
Kubernetes never runs a container on its own. It always wraps containers in a Pod. Every Deployment, Job and StatefulSet you will meet later is really a way of creating and managing Pods, so understanding them well makes the rest of the course much easier.
In this lesson you will write a Pod manifest, learn what the different statuses mean, build a Pod with an init container and a sidecar, and see how Kubernetes stops a Pod politely.
What a Pod is
A Pod is a group of one or more containers that always run together on the same node and share:
- One network identity. The Pod has one IP address. Containers in it reach each other on
localhost, and they cannot both listen on the same port. - Volumes. A volume defined in the Pod can be mounted into several of its containers, so they can share files.
- A life. They are scheduled together, started together and deleted together.
Most Pods have exactly one container. Put several containers in one Pod only when they are tightly coupled helpers of the same app, for example a log shipper next to a web server. Two independent apps, such as a web app and its database, belong in separate Pods so each can be scaled and updated on its own.
A Pod manifest
apiVersion: v1
kind: Pod
metadata:
name: menu
labels:
app: menu
spec:
containers:
- name: web
image: nginx:1.30-alpine
ports:
- containerPort: 80
env:
- name: SHOP_CITY
value: LucknowapiVersion: v1andkind: Podsay what kind of object this is.metadata.namemust be unique among Pods in the namespace;labelsare tags you will use to select Pods later.spec.containersis a list. Each container needs anameand animage.containerPortis mainly documentation; the container listens on whatever port its program opens.envsets environment variables, just likedocker run -e.
Save it as menu.yaml and run kubectl apply -f menu.yaml, which prints pod/menu created. You can override the image's command with command (replaces the Dockerfile ENTRYPOINT) and args (replaces CMD).
Phases and statuses
Every Pod has a phase, a simple summary of where it is in its life:
| Phase | Meaning |
|---|---|
Pending | Accepted, but not all containers are running yet (waiting for a node or for images) |
Running | Bound to a node and at least one container is running |
Succeeded | All containers finished with exit code 0 and will not restart |
Failed | All containers have stopped and at least one failed |
Unknown | The node stopped reporting, so the state cannot be read |
The STATUS column of kubectl get pods shows something more detailed. These are the ones you will see most:
| STATUS | What it usually means |
|---|---|
ContainerCreating | Image is downloading or volumes are being attached |
ErrImagePull, ImagePullBackOff | Wrong image name or tag, or a private registry without credentials |
CrashLoopBackOff | The container keeps exiting; Kubernetes waits longer before each restart |
OOMKilled | The container used more memory than its limit and was killed |
Completed | The container finished successfully (common for Jobs) |
Terminating | The Pod is shutting down |
Restart policy
spec.restartPolicy decides what the kubelet does when a container exits:
Always(the default): restart it, whatever the exit code. Right for servers.OnFailure: restart only after a non-zero exit code. Right for batch work.Never: do not restart.
Restarts happen in place on the same node, with growing delays. A restart policy does not protect you from a node failing: a bare Pod on a dead node is simply lost. That is why you will run real apps through a Deployment or another controller, never as bare Pods.
Init containers and sidecars
Init containers run one after another, each to completion, before the main containers start. They are good for setup work: waiting for a database, downloading files or preparing a config.
A sidecar container is a helper that starts before the main containers and keeps running beside them for the Pod's whole life. Since Kubernetes 1.33 sidecars are a stable feature: you declare one as an init container with restartPolicy: Always.
This Pod uses both. The init container writes a web page into a shared emptyDir volume (an empty folder that lives as long as the Pod), Nginx serves it, and a sidecar prints Nginx's access log:
apiVersion: v1
kind: Pod
metadata:
name: menu-board
spec:
initContainers:
- name: write-menu
image: busybox:1.37
command: ["sh", "-c", "echo '<h1>Today: masala chai Rs 20</h1>' > /work/index.html"]
volumeMounts:
- name: html
mountPath: /work
- name: log-reader
image: busybox:1.37
restartPolicy: Always
command: ["sh", "-c", "tail -F /logs/access.log"]
volumeMounts:
- name: logs
mountPath: /logs
containers:
- name: web
image: nginx:1.30-alpine
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: logs
mountPath: /var/log/nginx
volumes:
- name: html
emptyDir: {}
- name: logs
emptyDir: {}Apply it and check it twice, a few seconds apart:
kubectl apply -f menu-board.yaml
kubectl get pod menu-board
kubectl get pod menu-boardpod/menu-board created NAME READY STATUS RESTARTS AGE menu-board 0/2 Init:0/2 0 3s NAME READY STATUS RESTARTS AGE menu-board 2/2 Running 0 21s
Init:0/2 means none of the two init containers has finished starting yet. Later READY 2/2 counts the main container and the sidecar. Now send a request and read the sidecar's output:
kubectl port-forward pod/menu-board 8080:80 &
curl -s http://localhost:8080
kubectl logs menu-board -c log-reader<h1>Today: masala chai Rs 20</h1> 127.0.0.1 - - [01/Nov/2026:10:15:32 +0000] "GET / HTTP/1.1" 200 34 "-" "curl/8.16.0" "-"
(The Forwarding from lines are left out; your date and curl version will differ.) The page came from the init container, and the log line reached the sidecar through the shared logs volume. -c chooses which container's log to read.
Stopping a Pod politely
When a Pod is deleted, the kubelet sends SIGTERM to each container's main process and waits for the grace period, 30 seconds by default (terminationGracePeriodSeconds). Anything still running after that gets SIGKILL. Well-written apps catch SIGTERM, finish the requests they are serving, close database connections and exit. A preStop hook can run a command before SIGTERM is sent, for example a short sleep so that load balancers stop sending traffic first.
Common mistakes
- Running real apps as bare Pods. Nothing recreates them if their node dies. Use a Deployment.
- Putting the app and its database in one Pod. They cannot scale or update separately. Use two Pods.
- Expecting the Pod IP to stay the same. A replacement Pod gets a new IP. Use a Service to reach Pods.
- Ignoring SIGTERM in your app. Users see cut-off requests on every deploy.
Key points
- A Pod is one or more containers that share an IP address, volumes and a node; most Pods have one container.
- Pods have a phase (
Pending,Running,Succeeded,Failed,Unknown); theSTATUScolumn gives more detail such asCrashLoopBackOff. restartPolicyrestarts containers in place but cannot save a Pod whose node has died.- Init containers run to completion first; sidecars are init containers with
restartPolicy: Alwaysand run for the whole Pod life. - Deleting a Pod sends SIGTERM, waits the grace period (30 seconds by default), then sends SIGKILL.
Exercise
Write a Pod called exam-results with one main container running nginx:1.30-alpine. Add an init container from busybox:1.37 that waits until the DNS name results-db can be resolved, checking every 2 seconds (the shell loop is until nslookup results-db; do sleep 2; done). What will kubectl get pod exam-results show while no such name exists?
Show solution
apiVersion: v1
kind: Pod
metadata:
name: exam-results
spec:
initContainers:
- name: wait-for-db
image: busybox:1.37
command: ["sh", "-c", "until nslookup results-db; do sleep 2; done"]
containers:
- name: web
image: nginx:1.30-alpineWhile results-db cannot be resolved, the init container keeps looping, so the Pod shows READY 0/1 and STATUS Init:0/1, and Nginx never starts. As soon as a Service called results-db exists in the same namespace, the loop ends and the Pod becomes Running.
Quick Quiz
1. How do two containers in the same Pod talk to each other over the network?
Containers in one Pod share one IP address, so they use localhost and different ports.
2. Which restart policy suits a web server?
Servers should come back whatever the reason they stopped.
3. How do you declare a sidecar container in Kubernetes 1.33 and later?
Sidecars start before the main containers and keep running for the Pod's lifetime.
4. What does a Pod status of ImagePullBackOff usually point to?
Check the exact image reference and any image pull secret.
Learn Kubernetes with a mentor
Free lessons get you started. Our Cloud & DevOps training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
