Your first pod
Run something, then look at it.
A pod is the smallest thing you can run on Kubernetes. This lesson is one cycle: start a pod, watch it come up, look inside, then delete it. Run, check, look, delete. That is most of the daily job, whether you have one web server or a thousand. The scale changes; the four moves do not.
Start one and watch it come up
A few words first. A container is your app plus what it needs to run. A pod is the wrapper Kubernetes puts around that container, and most pods hold exactly one. A cluster is the group of machines; each worker is a node. You talk to the cluster with kubectl (Kubernetes control): type kubectl, then what you want done. The quickest start is kubectl run. Hand it an image (the packaged app in a registry) and it asks the cluster to start one copy. Then kubectl get pods lists what is running. You are waiting for Running.
$ kubectl run hello --image=nginxpod/hello created$ kubectl get podsNAME READY STATUS RESTARTS AGEhello 1/1 Running 0 12s
READY 1/1 means you asked for one container and one is up. Seeing Pending instead? Pending means no container is running yet, and in the first few seconds that is usually because the cluster hasn't picked a machine for the pod. For the full story, run kubectl describe. It tells you which node the pod landed on, the image it's using, and at the bottom a list of events that reads like parcel tracking: scheduled, image pulled, container started. Read that list from the top down and you can follow exactly what happened, in the order it happened.
$ kubectl describe pod helloName: helloNamespace: defaultNode: worker-1/10.0.0.5Status: RunningIP: 10.244.1.7Containers:hello:Image: nginxState: RunningEvents:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 30s default-scheduler Successfully assigned default/hello to worker-1Normal Pulling 29s kubelet Pulling image "nginx"Normal Pulled 25s kubelet Successfully pulled image "nginx"Normal Created 25s kubelet Created container: helloNormal Started 24s kubelet Started container hello
The kubelet in that list is a small program on every node. It is what actually starts your containers. When something will not start, Events are the first place to look. They usually name the problem in plain English, so you can often fix it without opening anything else.
When it won't start, read the failure
Not every pod starts cleanly, and the fastest way to trust these tools is to break something on purpose. The most common first stumble is a wrong image name or tag. Point a pod at a tag nobody ever published, then see what kubectl get pods says about it.
$ kubectl run broken --image=nginx:doesnotexistpod/broken created$ kubectl get podsNAME READY STATUS RESTARTS AGEbroken 0/1 ImagePullBackOff 0 40shello 1/1 Running 0 3m
READY 0/1 means none of your one container is up. ImagePullBackOff means it tried to download the image, failed, and is waiting longer between retries. kubectl logs will not help: no container started, so nothing printed. This is a describe job. Run it, then read Events at the bottom.
$ kubectl describe pod brokenName: brokenNamespace: defaultNode: worker-1/10.0.0.5Status: PendingContainers:broken:Image: nginx:doesnotexistState: WaitingReason: ImagePullBackOffEvents:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 60s default-scheduler Successfully assigned default/broken to worker-1Normal Pulling 44s (x3 over 59s) kubelet Pulling image "nginx:doesnotexist"Warning Failed 43s (x3 over 58s) kubelet Failed to pull image "nginx:doesnotexist": not foundWarning Failed 43s (x3 over 58s) kubelet Error: ErrImagePullNormal BackOff 16s (x4 over 58s) kubelet Back-off pulling image "nginx:doesnotexist"Warning Failed 16s (x4 over 58s) kubelet Error: ImagePullBackOff
The Warning lines tell the whole story. Notice that Status near the top still reads Pending even though the Scheduled event says this pod already landed on worker-1. Pending covers everything before a container runs, the image pull included, while the STATUS column in kubectl get pods reports the container's own reason instead, which is why that column said ImagePullBackOff. The kubelet asked the registry (the online store where container images are kept) for nginx:doesnotexist, was told there is no such image, and backed off. Nearly every pull failure is spelled out right there on the Image line: a typo in the name, or a tag that doesn't exist. Point it at a real tag like nginx:1.27 and the pod starts. Clean up the broken one with kubectl delete pod broken and you're back to a tidy slate. That's the pattern for most early failures. One more thing worth reading in that output: the (x3 over 59s) beside a reason is a retry count, so a number that keeps climbing means the node is still trying and still failing.
Look inside the running pod
Two commands show what the app is doing. kubectl logs prints what the container wrote, the same messages you would see on a laptop. Read that first when something misbehaves. kubectl exec runs a command inside the container. Add -it, put sh on the end, and you get an interactive shell inside the container. Type exit to leave.
$ kubectl logs hello/docker-entrypoint.sh: Configuration complete; ready for start up2026/07/16 10:22:31 [notice] 1#1: nginx/1.27.32026/07/16 10:22:31 [notice] 1#1: start worker processes$ kubectl exec -it hello -- sh# lsbin boot dev docker-entrypoint.sh etc home lib# exit$
Finished poking around? Clear it away. kubectl delete pod removes the pod and gives the node back the room it was taking up. Build the habit of deleting your test pods the moment you're done with them, so the cluster stays tidy and you aren't holding on to things you've forgotten about.
$ kubectl delete pod hellopod "hello" deleted$ kubectl get podsNo resources found in default namespace.
The reproducible way: a file you can re-run
kubectl run is great for a quick test, but it vanishes the moment you forget what you typed. What people actually do is write the pod down in a file and apply that file. The file is YAML, a plain-text format built to be easy for a person to read and edit. Three parts sit at the top of every Kubernetes file: apiVersion (which set of rules to read it by), kind (what you're making, a Pod here), and metadata (its name and labels, the name tag on the box). Under those comes the part that describes the thing itself, and what it is called depends on the kind. A Pod uses spec (what actually goes inside), a ConfigMap uses data, a Role uses rules. Save the file in git and anyone on your team can recreate the exact same pod next month.
apiVersion: v1kind: Podmetadata:name: hellolabels:app: hellospec:containers:- name: helloimage: nginx:1.27ports:- containerPort: 80
$ kubectl apply -f pod.yamlpod/hello created$ kubectl get podsNAME READY STATUS RESTARTS AGEhello 1/1 Running 0 9s
Your first pod is a trust exercise with two parts of the cluster: the scheduler, which decides where the pod goes, and the kubelet, which starts it once it lands. Four statuses cover nearly everything you'll meet early on. Pending means no container is running yet, whether the pod is still waiting for a node or already on one and pulling its image. ContainerCreating means the node is pulling or starting the image. CrashLoopBackOff means the process starts and keeps dying. ImagePullBackOff means the node can't fetch the image at all. Learn those four before you go near the exotic fields.
kubectl run is the fastest way to get a pod under your hands. A YAML file is the way to get the same pod twice. Switch to YAML as soon as you care about labels, probes (the small health checks Kubernetes runs against your container), or resource requests, and for anything you'll re-apply tomorrow.
An nginx pod with no Service in front of it cannot be reached from outside the cluster, but it is still a real process on a real node: it holds memory, it runs as whatever user the image picked, and anything else inside the cluster can reach it on its pod IP. That is enough to matter the moment you forget it is there. Treat even first pods as real workloads. Delete what you create, and never leave a debug image sitting there Running in a production namespace.
Try this
Start a Pod, watch the status flip to Running, force a bad image and read what describe tells you, then delete both cleanly. The --restart=Never below sets the pod's restart policy: if the container exits, Kubernetes leaves it stopped instead of starting it again. The earlier examples used the default, Always, which is what turns a container that keeps dying into CrashLoopBackOff.
$ kubectl run hello --image=nginx:1.27 --restart=Neverpod/hello created$ kubectl get pods -wNAME READY STATUS RESTARTS AGEhello 0/1 ContainerCreating 0 1shello 1/1 Running 0 4s# Ctrl+C$ kubectl run broken --image=nginx:doesnotexist --restart=Neverpod/broken created$ kubectl get pod brokenNAME READY STATUS RESTARTS AGEbroken 0/1 ImagePullBackOff 0 35s$ kubectl describe pod broken | grep -A 6 'Events:'Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 35s default-scheduler Successfully assigned default/broken to worker-1Normal Pulling 20s (x3 over 34s) kubelet Pulling image "nginx:doesnotexist"Warning Failed 19s (x3 over 33s) kubelet Failed to pull image "nginx:doesnotexist": not foundWarning Failed 19s (x3 over 33s) kubelet Error: ErrImagePull$ kubectl delete pod hello brokenpod "hello" deletedpod "broken" deleted
Takeaway
A pod walks from Pending to ContainerCreating to Running, or it stops at a status that names the trouble. When it stops, the Events at the bottom of kubectl describe pod tell you why. Reach for kubectl run when you're poking around, write a YAML file for anything you'll want again, and delete every lab pod you started before you shut the laptop.