# kubectl Cheat Sheet — Core Resources

> **Tool:** kubectl
> **Category:** Containers & Orchestration
> **Verified against:** kubectl v1.34.0 (client), flags verified via `kubectl <cmd> --help` run locally, 2026-08-29
> **Official docs:** https://kubernetes.io/docs/reference/kubectl/

## 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

```bash
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 label
```

## Inspecting a resource in detail 🔍

```bash
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 JSON
```

`describe` 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

```bash
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 anything
```

> [!TIP]
> **`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

```bash
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 first
```

> [!CAUTION]
> `--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

```bash
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

```bash
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 YAML
```

## Autoscaling a workload (HPA) ⚙️

```bash
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-deployment
```

`kubectl 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

```bash
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 only
```

> [!NOTE]
> 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

```bash
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 change
```

`apply -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

```bash
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

```bash
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:

```bash
kubectl set image deployment/my-app app=myrepo/app:v2.3.1
kubectl rollout status deployment/my-app --timeout=180s
```

> [!IMPORTANT]
> **`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 `apply` and `create` are interchangeable** — `create` fails outright on an existing resource;
  only `apply` is idempotent.
- **Checking `kubectl logs my-pod` right 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 `create` shortcut** — they need `apply -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.
