Your first pod
Run something, then look at it.
A pod is the smallest thing you can run on Kubernetes. Ordering a single dish at a restaurant is the closest everyday match: you ask for one thing, it turns up, and you can check on it while it cooks. That's this whole lesson. Start one pod, watch it come up, look inside it, then clear it away. Run, check, look, delete. That four-step rhythm is most of the day-to-day job, whether you're running one small web server or a thousand busy ones. Get comfortable with it on one pod now, and the big clusters later are the same four moves at a bigger scale.
Start one and watch it come up
A few words first, so nothing below trips you up. A container is your app boxed up with everything it needs to run, like a ready meal that heats the same way in any oven. A pod is the wrapper Kubernetes puts around that container, and most pods hold exactly one. Kubernetes spreads your work across a group of machines called a cluster, and each worker machine in that cluster is a node. You talk to the cluster through a command-line tool called kubectl (short for Kubernetes control): you type kubectl, then what you want done. The quickest way to start a pod is kubectl run. Hand it an image (your packaged-up app, sitting in a registry waiting to be downloaded) and it asks the cluster to start one copy. Then kubectl get pods lists what's running so you can watch the status. You're waiting for one word: 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 running on every node, and it's the thing that actually starts your containers. It's the cook in the kitchen: the order arrives, the kubelet gets it going. When something won't start, those Events are the first place to look. They usually name the problem in plain English, so you'll 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 is Kubernetes telling you it tried to download the image, couldn't, and is now waiting a while before trying again (backing off, the way you stop pressing a doorbell nobody answers). Reaching for kubectl logs won't help here. No container ever started, so nothing has printed a single line. This is a describe job. Run it, then read the 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 let you see what your app is doing. kubectl logs prints whatever the container wrote out, the same messages you'd see running the app on your own laptop. Read this first when something misbehaves. kubectl exec goes further and runs a command inside the container. Add the -it flag, put sh on the end, and you get an interactive shell, a text prompt for typing commands inside the container, as if you stepped into the kitchen and looked in the pans yourself. Type exit to step back out.
$ 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.