# kubectl Cheat Sheet — Core Resources

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

Getting, describing, creating, and deleting the resources you touch every day — pods, deployments, services — plus applying manifests.

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

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

## 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 create -f pod.yaml                      # create only — fails if it already exists
kubectl create deployment my-app --image=nginx:1.27
```

`apply` is the standard for anything you'll re-run (it diffs against the last-applied state and only changes what differs); `create` is a one-shot, fails on a resource that already exists. Prefer `apply` for anything under version control.

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

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

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

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

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 generic resource kinds.

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

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