Core Resources
.mdVerified against kubectl v1.34.0 (client), flags verified via `kubectl <cmd> --help` run locally, 2026-08-29 · official docs
What it is and where it fits 🎯#
kubectl is the Kubernetes API server's command-line client — every action it takes is an HTTP call against
the API server, the same API a controller, an operator, or the dashboard would use. There's no special
back-channel: kubectl apply and a GitOps controller's reconcile loop both ultimately do the same kind of
PUT/PATCH against the same endpoint. Understanding that reframes a lot of "why did this happen" questions
— kubectl isn't magic, it's a well-designed wrapper around a REST API, and kubectl explain/-o yaml/-v=8
(verbose HTTP tracing) are all ways to see exactly what it's actually sending.
This page covers getting, describing, creating, and deleting the resources you touch every day — pods, deployments, services — plus applying manifests. See the companion pages in this set for networking/RBAC/config, day-to-day debugging, and node administration/advanced usage.
Listing resources#
kubectl get pods
kubectl get pods -o wide # + node, IP, and readiness columns
kubectl get pods -n my-namespace
kubectl get pods --all-namespaces
kubectl get deployments,services # multiple resource types in one call
kubectl get pods -l app=nginx # filter by label selector
kubectl get pods -w # watch for changes live
kubectl get pods --show-labels # print every label as a trailing column
kubectl get pods --field-selector status.phase=Running # filter on a field, not a labelInspecting a resource in detail 🔍#
kubectl describe pod my-pod
kubectl describe deployment my-deployment
kubectl get pod my-pod -o yaml # full resource manifest as YAML
kubectl get pod my-pod -o json # full resource manifest as JSONdescribe includes recent Events for the resource — often the fastest way to see why a pod is stuck
(ImagePullBackOff, failed readiness probe, insufficient node resources) without a separate get events call.
Sample describe pod output shape (illustrative — the actual fields shown depend on the pod's real state):
Name: my-pod
Namespace: default
Status: Running
IP: 10.244.1.7
Containers:
app:
Image: myapp:v2
State: Running
Ready: True
Restart Count: 0
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m default-scheduler Successfully assigned default/my-pod to node-1
Normal Pulled 2m kubelet Container image "myapp:v2" already present on machine
Normal Started 2m kubelet Started container app
Applying and creating resources#
kubectl apply -f deployment.yaml # create or update to match the file (idempotent)
kubectl apply -f ./manifests/ # apply every manifest in a directory
kubectl apply -f ./manifests/ --recursive # + every subdirectory too
kubectl create -f pod.yaml # create only — fails if it already exists
kubectl create deployment my-app --image=nginx:1.27
kubectl apply -f deployment.yaml --dry-run=server # validate against the real API server without persisting anythingTip
apply is the standard for anything you'll re-run — it diffs against the last-applied state (stored
in an annotation) and only changes what differs, rather than blindly overwriting. create is a one-shot,
and fails outright on a resource that already exists. Prefer apply for anything under version control —
create is really only the right tool for a genuinely one-off imperative object.
Deleting resources#
kubectl delete pod my-pod
kubectl delete -f deployment.yaml
kubectl delete pods -l app=nginx # delete everything matching a label
kubectl delete pod my-pod --grace-period=0 --force # skip graceful termination (last resort)
kubectl delete pods --all -n staging # delete every pod in a namespace — be certain of the namespace firstCaution
--grace-period=0 --force skips the container's normal shutdown sequence entirely (no SIGTERM, no time
for in-flight requests to finish) and can leave a stateful workload in an inconsistent state if it was mid
write. It's a genuine last resort for a pod that's stuck in Terminating and won't go away any other way —
not a routine flag to reach for because a graceful delete "feels slow."
Logs and exec#
kubectl logs my-pod
kubectl logs my-pod -c my-container # a specific container in a multi-container pod
kubectl logs my-pod -f # stream/follow
kubectl logs my-pod --previous # the PREVIOUS container instance's logs — the one to check after a crash/restart
kubectl logs deployment/my-deployment --all-pods=true
kubectl exec my-pod -- date # run a one-off command
kubectl exec -it my-pod -- /bin/bash # interactive shell-it (interactive + tty) is what makes exec behave like a real shell session instead of a single
non-interactive command — forgetting it against a shell command leaves you unable to type anything back.
Tip
--previous is the single most useful flag for debugging a CrashLoopBackOff. By the time you notice
and run kubectl logs my-pod, the container has often already restarted — plain logs shows the new
instance's (probably empty, or "just started") output, not the crash. --previous shows the terminated
instance's logs, which is where the actual error usually is.
Editing and scaling#
kubectl edit deployment my-deployment # opens the live resource in $EDITOR
kubectl scale deployment my-deployment --replicas=5
kubectl set image deployment/my-deployment my-container=myrepo/app:v2 # roll a new image without editing YAML
kubectl set env deployment/my-deployment LOG_LEVEL=debug # add/update an env var without editing YAMLAutoscaling a workload (HPA) ⚙️#
kubectl autoscale deployment my-deployment --min=2 --max=10 --cpu-percent=70
kubectl get hpa # list HorizontalPodAutoscalers and their current/target metrics
kubectl describe hpa my-deployment
kubectl delete hpa my-deploymentkubectl autoscale tries the autoscaling/v2 API first (CPU + memory + custom metrics) and falls back to
v1 (CPU only) if the cluster doesn't support it — --cpu-percent alone always works, memory-based targets
need v2. autoscale also works against a ReplicaSet or ReplicationController, not just a Deployment.
StatefulSets, DaemonSets, Jobs, and CronJobs#
kubectl get statefulsets
kubectl get daemonsets
kubectl scale statefulset my-db --replicas=3 # StatefulSets scale like Deployments...
kubectl rollout status statefulset/my-db # ...and support the same rollout commands
kubectl rollout restart daemonset/my-agent # roll every node's pod without a manifest change
kubectl create job my-job --image=busybox -- date # one-off Job
kubectl create job my-job-from-cj --from=cronjob/my-cronjob # run a CronJob's Job definition immediately, on demand
kubectl create cronjob my-cronjob --image=busybox --schedule="*/5 * * * *" -- date
kubectl get cronjobs
kubectl get jobs --field-selector status.successful=1 # completed Jobs onlyNote
There's no kubectl create statefulset/kubectl create daemonset shortcut the way there is for
deployment/job/cronjob — StatefulSets and DaemonSets are manifest-only resources, created with
kubectl apply -f (they need a volumeClaimTemplates/spec.selector shape that doesn't map cleanly onto
CLI flags). Once created, though, get/describe/scale/rollout all work on them exactly like
Deployments, since they're all just generic resource kinds under the hood.
Kustomize overlays#
kubectl apply -k ./overlays/production/ # build and apply a kustomization directory
kubectl kustomize ./overlays/production/ # render the final manifest to stdout without applying
kubectl diff -k ./overlays/production/ # preview what apply -k would changeapply -k is for a kustomization.yaml-based directory (patches/overlays layered on a base) — it can't be
combined with -f or -R in the same call. kubectl kustomize (no apply) is the equivalent of a dry-run
render, useful for reviewing the generated manifest in a PR before it ever touches the cluster.
Note
The kustomize version embedded inside kubectl (v5.7.1 as of this kubectl release) tends to lag behind
the standalone kustomize CLI's latest release. See this series' own kustomize cheat sheet for the
standalone tool and when its newer features are worth reaching for over the embedded version.
Diffing before you apply#
kubectl diff -f deployment.yaml # unified diff between the live object and what apply would produce
kubectl diff -f ./manifests/kubectl diff shells out to the system diff (or KUBECTL_EXTERNAL_DIFF if set, e.g. colordiff) and exits
1 if there are differences, 0 if none — script it into a CI gate the same way you'd use terraform plan's
exit code.
Labels and annotations#
kubectl label pods my-pod tier=frontend # add a label
kubectl label pods my-pod tier=backend --overwrite # change an existing label (fails without --overwrite)
kubectl label pods my-pod tier- # remove a label (trailing "-")
kubectl label pods -l app=nginx --all env=prod # label everything matching a selector
kubectl annotate pods my-pod description="handles checkout" # annotations can hold longer/structured values
kubectl annotate pods my-pod description- # remove an annotation (no --overwrite needed)Labels are for selection (used by selectors on Services, Deployments, -l filters) and are capped at 63
characters; annotations are for arbitrary metadata (build SHAs, owner contacts, tool-specific config) that
nothing selects on. Reach for a label only if something will actually query on it.
Real-world scenario: zero-downtime image rollout, verified before moving on#
A routine deploy shouldn't be "run one command and hope" — chain the roll with an explicit rollout check:
kubectl set image deployment/my-app app=myrepo/app:v2.3.1
kubectl rollout status deployment/my-app --timeout=180sImportant
set image returns immediately once the API server accepts the change — it does NOT wait for the new
pods to actually become Ready. rollout status is what blocks until the rollout genuinely completes (or
fails and times out), which is the signal a deploy script or CI job should actually gate on, not the exit
code of set image alone. See the companion Debugging & Troubleshooting page for rollout undo when a
rollout doesn't converge.
Common pitfalls#
- Assuming
applyandcreateare interchangeable —createfails outright on an existing resource; onlyapplyis idempotent. - Checking
kubectl logs my-podright after a crash and seeing nothing useful — reach for--previous. - Treating
set image's success as "the deploy is done" — see the IMPORTANT callout above. - Forgetting DaemonSets/StatefulSets have no
createshortcut — they needapply -f, unlike Deployments/ Jobs/CronJobs.
When to reach for something else#
For anything beyond a quick ad-hoc lookup or a single imperative change, prefer keeping the manifest under
version control and using apply (or a GitOps controller on top of it) over the create/edit/set
imperative commands on this page — they're genuinely useful for exploration and incident response, but drift
silently out of sync with any git-tracked source of truth if used as the primary way to manage a workload
long-term.