Pod in depth: command, args, restartPolicy
How to override how a container starts, and what the cluster does after it finishes.
In the last lesson you started a Pod and kept the container alive with the
line command: ["sleep", "3600"]. Now let's look at what that line actually
does, how to pick the program a container runs without rebuilding the
image, and what Kubernetes does once the container has finished.
Concepts
- ENTRYPOINT and CMD - two image settings from the Dockerfile. ENTRYPOINT sets the program, CMD sets its default arguments (or the command itself, if ENTRYPOINT is empty).
- command and args - container fields in the Pod manifest. They override the image's ENTRYPOINT and CMD.
- restartPolicy - a field in the Pod's
spec: what to do once the container has finished. - kubelet - the Kubernetes agent on a node that starts and restarts containers. A node is a cluster machine that runs Pods.
- Exit code - the number a program finishes with.
0means success, any other number means an error.
command and args
Every image has a default program it runs (the Dockerfile's ENTRYPOINT and
CMD). The command and args fields on a container override that
behavior.
command replaces the default program. args sets its arguments.
spec:
containers:
- name: app
image: busybox:1.36.1
command: ["sh", "-c"]
args: ["echo hi; sleep 3600"]
Here the container runs sh -c "echo hi; sleep 3600": it prints hi and
sleeps for an hour. sh -c means "run this string as a shell script".
Without -c, the shell would decide the string is the name of a script
file, and wouldn't find it.
You can put the whole command in command if that's more convenient:
command: ["sh", "-c", "echo hi; sleep 3600"]
Each list item becomes a separate argument. Kubernetes itself never splits
a string into words: ["sleep", "3600"] and ["sleep 3600"] are different
things. In the second case the cluster will look for a program literally
named sleep 3600, with a space in it.
The Docker equivalent: command in a Pod is the container's ENTRYPOINT,
args is its CMD.
| Pod field | What it overrides | What it sets |
|---|---|---|
command | ENTRYPOINT | the program itself |
args | CMD | the program's arguments |
What runs for each combination:
- Neither
commandnorargs: whatever the image specifies runs. - Only
args: the image's ENTRYPOINT stays, CMD is replaced byargs. - Only
command:commandruns, and both the image's ENTRYPOINT and CMD are ignored. - Both fields:
commandruns withargsas its arguments.
busybox has an empty ENTRYPOINT, and CMD equal to ["sh"]. You can see
this in the image itself:
docker image inspect busybox:1.36.1 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'
null ["sh"]
That's why even args: ["sleep", "3600"] without command starts
sleep 3600 on busybox: there's nowhere to attach arguments to, so args
becomes the whole command. On an image with an ENTRYPOINT (nginx, say, or
a Go application), args would become arguments to its program instead.
kubectl run can also set the command. Everything after -- goes into
args. With the --command flag, it goes into command instead:
kubectl run sleeper --image=busybox:1.36.1 --command -- sleep 3600
Why override the command
A base image often supports more than one scenario. The same busybox can
run a web server, do a one-off task, or just stay alive for debugging.
command/args pick which one, without rebuilding the image.
That's where the typical sleep trick comes from. Without it the
container exits right after the echo. kubelet starts it again, it exits
again, and so on: the Pod's phase stays Running, while STATUS shows
CrashLoopBackOff.
restartPolicy
restartPolicy is a field in spec that says what to do once a container
has finished. It's usually set once for the whole Pod, and it applies to
every container in it. It has three values.
Always- always restart, even after exiting with code 0. The default value.OnFailure- restart only on a nonzero exit code.Never- never restart.
restartPolicy | Exit code 0 | Exit with an error |
|---|---|---|
Always | restart | restart |
OnFailure | leave as is | restart |
Never | leave as is | leave as is |
spec:
restartPolicy: OnFailure
containers:
- name: app
image: busybox:1.36.1
command: ["sh", "-c", "exit 1"]
This container exits with code 1, and kubelet will keep restarting it
again and again. Set exit 0, and after the very first run the Pod moves
to phase Succeeded.
A container with sleep 3600 finishes after an hour. With
restartPolicy: Always, kubelet starts it again. That's why a web service
with Always doesn't go down from a single crash: the container dies,
kubelet brings it back up, and the Pod answers requests again.
The choice of policy depends on the job. A long-running service needs
Always. A one-off task that should run once and not repeat without an
error fits OnFailure or Never (such tasks are usually set up as a Job,
a separate lesson).
As of Kubernetes 1.35, restartPolicy can also be set on an individual
container, next to image. Then it overrides the Pod's policy for that
container. This is the ContainerRestartRules beta feature, enabled by
default. In this course, a policy at the Pod level is enough.
You can't change restartPolicy, command, or args on a Pod that
already exists. kubectl apply with a new value will return an error:
The Pod "app" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`,...
For such an edit to take effect, the Pod gets deleted
(kubectl delete pod <name>) and created again from the fixed file.
How this shows up in the cluster
The RESTARTS column in kubectl get pod shows how many times the
container has restarted. If the number keeps climbing, the container is
crashing and the policy keeps bringing it back. Between attempts kubelet
waits, and the pause grows: 10s, 20s, 40s, and on up to 5 minutes. While
it's waiting, STATUS shows CrashLoopBackOff.
Here's what Pods with the same image, but different policies and exit codes, showed a minute after starting:
restartPolicy | Exit code | STATUS | RESTARTS | Phase |
|---|---|---|---|---|
Always | 0 | Completed or CrashLoopBackOff | 2 and climbing | Running |
OnFailure | 0 | Completed | 0 | Succeeded |
OnFailure | 1 | Error or CrashLoopBackOff | 2 and climbing | Running |
Never | 1 | Error | 0 | Failed |
Look at the first row. CrashLoopBackOff can happen to a container that
ran with no errors at all: with Always, kubelet restarts it anyway.
The exit code of the last run shows up in kubectl describe pod <name>,
in the container block, in the Last State and Exit Code lines. What
the program managed to print shows up in kubectl logs <name>. Debugging
crashes gets its own lessons later in the course.
Common mistakes
- The whole script in one
commandelement withoutsh -c, for examplecommand: ["echo hi; sleep 1"]. There's no such program, the container doesn't start: STATUSStartError, withexecutable file not found in $PATHinkubectl describe pod. - Running a one-off task without
restartPolicy. The default isAlways, and a task that finished successfully runs again and again. - Changing
restartPolicyorcommandin the file and runningkubectl applyon top of an existing Pod. You getForbidden: the Pod needs to be recreated. - Seeing
CrashLoopBackOffand looking for a bug in the code. Check the exit code first: if it's 0, the program ran fine, and the issue is the restart policy.
Homework
Practice the theory on your own cluster: 2 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.
./run.sh start k8s-pod-deep # set up the starting state
./run.sh verify k8s-pod-deep # check the current step
In short
commandoverrides the image's ENTRYPOINT,argsoverrides CMD.- Only
argswithoutcommandis passed to the image's ENTRYPOINT. busybox has an empty ENTRYPOINT, so thereargsbecomes the whole command. - Each list item is a separate argument. Run a multi-command script with
sh -c. - A long-running process like
sleep 3600is there so the container doesn't exit right away and loop through restarts. restartPolicy:Always(default),OnFailure,Never. You can't change it on a Pod that already exists, only recreate the Pod.- A climbing RESTARTS column means a restart loop; between attempts
STATUS shows
CrashLoopBackOff.
What you'll need for the tasks
- Docker, kind v0.31.0 or newer, kubectl v1.35 or v1.36. Each lab's
setup/up.shbrings up its own cluster. - The Pod manifest from the last lesson plus the
command,args, andrestartPolicyfields from this one. - The commands
kubectl apply -f,kubectl get pod,kubectl logs,kubectl describe pod,kubectl delete pod. - Any text editor for YAML (
nano, VS Code).
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress