## What it is

kubectl is the command-line client for talking to a Kubernetes cluster. Every cluster operation — inspecting pods, rolling out a new image, port-forwarding a service for debugging, rolling back a bad deploy — funnels through it. If you ship containerised apps on Kubernetes, kubectl is the daily driver.

Most kubectl cheatsheets read like an alphabetised dump of every flag. This one is organised in the order an engineer actually uses the commands across a deploy: cluster check → namespace → workloads → services → config → rollout → debug → cleanup. The same flow you'd expect from a real deploy pipeline.

## Quick reference

### Cluster and context

```bash
kubectl cluster-info                                # API server endpoints, sanity check
kubectl version --short                             # client + server versions
kubectl config get-contexts                         # all configured clusters
kubectl config current-context                      # which cluster you're pointed at now
kubectl config use-context prod-eu                  # switch clusters
kubectl config set-context --current --namespace=billing   # default namespace for this context

kubectl get nodes                                   # all worker nodes
kubectl get nodes -o wide                           # adds IPs, OS, kernel, runtime
kubectl describe node ip-10-0-1-23                  # capacity, conditions, scheduled pods
kubectl top nodes                                   # CPU / memory usage (needs metrics-server)
```

Always confirm `current-context` before any write command. The cost of running `kubectl delete` against the wrong cluster is a war story you don't want.

### Namespaces

```bash
kubectl get namespaces                              # list namespaces
kubectl create namespace staging
kubectl get pods -n staging                         # one-off namespace override
kubectl get pods -A                                 # every pod across every namespace
kubectl delete namespace staging                    # destructive — wipes everything in it
```

### Pods

```bash
kubectl get pods                                    # pods in current namespace
kubectl get pods -o wide                            # node + pod IP columns
kubectl get pods -l app=api                         # label selector
kubectl get pods --field-selector status.phase=Running
kubectl get pod api-7c9d-x4q2 -o yaml               # full manifest with status

kubectl describe pod api-7c9d-x4q2                  # events + container state, the debug starting point
kubectl logs api-7c9d-x4q2                          # last container log
kubectl logs -f api-7c9d-x4q2                       # follow (tail -f)
kubectl logs --previous api-7c9d-x4q2               # logs from a crashed container
kubectl logs -l app=api --tail=100                  # aggregate logs across pods by label

kubectl exec -it api-7c9d-x4q2 -- sh                # shell into the container
kubectl exec api-7c9d-x4q2 -- env                   # run one command, no TTY
kubectl port-forward pod/api-7c9d-x4q2 8080:3000    # local 8080 → pod 3000
kubectl cp api-7c9d-x4q2:/tmp/heap.dump ./heap.dump # copy file out of a pod
```

### Deployments

```bash
kubectl create deployment api --image=registry.example.com/api:1.4.2
kubectl apply -f deploy/api.yaml                    # declarative — the canonical workflow
kubectl apply -f deploy/                            # whole directory
kubectl get deployments
kubectl describe deployment api                     # rollout state, replica counts, conditions

kubectl scale deployment/api --replicas=5           # imperative scale
kubectl set image deployment/api api=registry.example.com/api:1.4.3   # ship a new image
kubectl edit deployment/api                         # opens manifest in $EDITOR (last resort)
kubectl delete deployment api
```

`apply -f` should be the default. `set image` and `scale` are convenient for one-offs but drift from your Git-tracked manifests — anything you'd want to reproduce belongs in the YAML and lives under version control.

### Services and networking

```bash
kubectl get svc                                     # services in current namespace
kubectl expose deployment api --port=80 --target-port=3000 --type=ClusterIP
kubectl describe svc api                            # endpoints + selector
kubectl get endpoints api                           # which pods the service actually points to

kubectl port-forward svc/api 8080:80                # forward through the service (load balanced)
kubectl proxy                                       # local HTTP proxy to the API server on :8001
kubectl get ingress -A                              # all ingresses, useful for debugging routing
```

If `kubectl get endpoints` is empty for a service, the selector doesn't match any pod labels — that's the bug, not the ingress.

### ConfigMaps and Secrets

```bash
kubectl create configmap api-config --from-literal=LOG_LEVEL=info --from-literal=FEATURE_X=true
kubectl create configmap api-config --from-file=config.yaml         # whole file
kubectl create configmap api-config --from-env-file=.env            # one key per env var
kubectl get configmap api-config -o yaml

kubectl create secret generic api-secrets \
  --from-literal=DATABASE_URL='postgres://...' \
  --from-literal=JWT_SECRET='...'
kubectl create secret docker-registry ghcr \
  --docker-server=ghcr.io --docker-username=$USER --docker-password=$GITHUB_TOKEN
kubectl get secrets
kubectl get secret api-secrets -o jsonpath='{.data.DATABASE_URL}' | base64 -d   # decode one key
```

Secrets are base64-encoded, not encrypted. Treat the YAML as sensitive — don't commit it. For real encryption-at-rest, enable etcd encryption providers or use Sealed Secrets / External Secrets Operator.

### Rollouts and rollbacks

This is where kubectl earns its keep on production deploys.

```bash
kubectl rollout status deployment/api               # blocks until rollout completes or fails
kubectl rollout history deployment/api              # revision list
kubectl rollout history deployment/api --revision=4 # what changed in revision 4
kubectl rollout undo deployment/api                 # roll back to previous revision
kubectl rollout undo deployment/api --to-revision=3 # roll back to a specific revision
kubectl rollout restart deployment/api              # recreate all pods (picks up new ConfigMap/Secret)
kubectl rollout pause deployment/api                # pause mid-rollout (e.g. before canary cutover)
kubectl rollout resume deployment/api
```

`rollout restart` is the under-documented command that matters most in practice: ConfigMap and Secret changes do not automatically recreate the pods that mount them. Without a restart, your "deploy" updates the ConfigMap object but the running pods keep the old values until their next eviction.

Kubernetes' rolling-update strategy gives you [zero downtime deployments](https://www.deployhq.com/features/zero-downtime-deployments) by default — old pods only terminate after new pods pass readiness probes. If a rollout goes sideways, `rollout undo` gives you a fast manual rollback, and DeployHQ's [one-click rollback](https://www.deployhq.com/features/one-click-rollback) wraps the same pattern across both your Kubernetes manifests and any non-Kubernetes hosts in the same release.

### Debugging

```bash
kubectl get events --sort-by=.lastTimestamp         # cluster-wide event timeline
kubectl get events -n billing --field-selector type=Warning
kubectl describe pod <name>                         # events scoped to one pod (the 80% case)

kubectl top pods                                    # CPU + memory per pod (needs metrics-server)
kubectl top pods --containers                       # per-container breakdown
kubectl top pods --sort-by=memory

kubectl debug node/ip-10-0-1-23 -it --image=ubuntu  # ephemeral debug pod with host access
kubectl debug api-7c9d-x4q2 -it --image=busybox --target=api   # sidecar into a running pod

kubectl get pods --watch                            # live updates as state changes
kubectl wait --for=condition=Ready pod/api-7c9d-x4q2 --timeout=120s
```

When a pod is `CrashLoopBackOff`, the loop is `describe` → `logs --previous` → fix → `apply -f`. Reaching for `kubectl edit` should be a last resort because it drifts from your manifests.

### Apply, diff, delete

```bash
kubectl apply -f deploy/                            # declarative apply, idempotent
kubectl diff -f deploy/api.yaml                     # what would change vs current cluster state
kubectl apply --dry-run=server -f deploy/api.yaml   # full server-side validation, no write
kubectl apply -k overlays/production                # apply a Kustomize overlay

kubectl delete -f deploy/api.yaml                   # delete everything in the manifest
kubectl delete pod api-7c9d-x4q2                    # let the Deployment recreate it
kubectl delete pod api-7c9d-x4q2 --grace-period=0 --force   # nuclear, skip graceful shutdown
kubectl delete deployment api --cascade=orphan      # delete deployment, keep pods
```

`kubectl diff` before `kubectl apply` is the single best habit on a shared cluster. It catches manifest drift, accidental field changes, and "is this even pointing at the right cluster?" before you write anything.

### Kustomize

```bash
kubectl kustomize overlays/production               # render to stdout, no apply
kubectl apply -k overlays/production                # render and apply
kubectl delete -k overlays/production
```

Kustomize lives inside kubectl now — no separate binary needed. Use it before reaching for Helm if your manifests just need environment-specific patches.

### Output filtering and jsonpath

```bash
kubectl get pods -o json | jq '.items[].metadata.name'
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
kubectl get pods --no-headers -o custom-columns=:metadata.name   # names only, scriptable

kubectl get deploy api -o jsonpath='{.spec.template.spec.containers[0].image}'   # current image
kubectl get pod api-7c9d-x4q2 -o jsonpath='{.status.containerStatuses[0].restartCount}'
```

`jsonpath` is faster and dependency-free for scripts; pipe through `jq` when the query gets complex. Both beat parsing `kubectl describe` output with regex.

---

## Deployment workflows

The reference above is portable — every Kubernetes tutorial has those commands. The patterns below are the ones that matter once a manifest leaves your editor and has to survive a real production cluster.

### Declarative deploys with `kubectl diff` as the gate

The reliable production pattern is `diff` → `apply`, every time:

```bash
# from the repo root, on a deploy branch
kubectl config use-context prod
kubectl config set-context --current --namespace=billing

kubectl diff -f deploy/                             # review every change before applying
kubectl apply -f deploy/                            # apply the whole directory
kubectl rollout status deployment/api --timeout=5m  # block until healthy
```

If `rollout status` exits non-zero, your deploy hook should fail loudly — that's the trigger for an automated `kubectl rollout undo`. The pattern works equally well wrapped inside a CI/CD pipeline or a [DeployHQ build pipeline](https://www.deployhq.com/features/build-pipelines) running `kubectl` against your cluster.

### Image tagging for safe rollback

Mirror the Docker rule: never deploy `:latest` to production. Always tag images with the immutable build SHA and reference that SHA in the manifest:

```yaml
# deploy/api.yaml — SHA-pinned, not :latest
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:build-abc123def
          ports:
            - containerPort: 3000
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              memory: 512Mi
```

With a SHA-pinned image, `kubectl rollout undo` is genuinely safe — Kubernetes brings back the previous ReplicaSet pointing at the previous SHA, no rebuild, no dependency drift. With `:latest`, "rollback" rolls forward to whatever `:latest` resolves to today.

### Readiness vs liveness probes — get these right

Mix them up and rollouts misbehave in subtle ways:

- **Readiness** controls service traffic. Failing readiness removes the pod from the service endpoints — used to gate rolling updates.
- **Liveness** controls restarts. Failing liveness gets the container killed and restarted by the kubelet.

If your liveness probe fails during a slow boot, the kubelet will kill the pod before it ever becomes ready, and your rollout will loop forever. Use `initialDelaySeconds` plus a separate `startupProbe` for slow-starting apps instead of cranking liveness timeouts.

### Single-cluster blue/green with two Services

For low-risk cutovers without a service mesh, run two Deployments side by side and flip the service selector:

```bash
# blue is live, green is the new version
kubectl apply -f deploy/api-green.yaml              # new pods with label version=green
kubectl rollout status deployment/api-green
kubectl port-forward deployment/api-green 8080:3000 # smoke test
# flip traffic
kubectl patch svc api -p '{"spec":{"selector":{"app":"api","version":"green"}}}'
# rollback if it goes wrong
kubectl patch svc api -p '{"spec":{"selector":{"app":"api","version":"blue"}}}'
```

The whole cutover is one `kubectl patch` — under a second of traffic flip, instant rollback. Once green is stable for your soak period, delete the blue Deployment.

### `kubectl run` for one-off jobs

Running migrations or data scripts in-cluster is safer than tunnelling out of the network:

```bash
kubectl run -it --rm migrate \
  --image=registry.example.com/api:build-abc123def \
  --restart=Never \
  --env="DATABASE_URL=$DATABASE_URL" \
  -- npm run migrate:up

kubectl run -it --rm dbshell \
  --image=postgres:16-alpine \
  --restart=Never \
  -- psql "$DATABASE_URL"
```

`--rm` cleans up the pod automatically on exit. For repeatable jobs, use a real `Job` or `CronJob` manifest instead — `kubectl run` is for ad-hoc work.

---

## Common errors and fixes

| Error | Cause | Fix |
|---|---|---|
| `Unable to connect to the server: dial tcp ...: i/o timeout` | Wrong context, expired credentials, or VPN not connected | `kubectl config current-context` then `kubectl config view --minify`. Re-auth (`aws eks update-kubeconfig`, `gcloud container clusters get-credentials`, etc.) before retrying. |
| `error: the server doesn't have a resource type "..."` | CRD not installed, or you're talking to an old cluster | `kubectl api-resources \| grep <name>`. Install the operator or upgrade the CRD. |
| Pod stuck `Pending` | No node has the requested CPU/memory, or PVC can't bind | `kubectl describe pod <name>` — the Events section names the reason. Lower `requests`, add nodes, or fix the StorageClass. |
| Pod stuck `ContainerCreating` | Image pull failure, missing Secret, or volume mount issue | `kubectl describe pod <name>` again. `ErrImagePull` means a registry credential problem — check the `imagePullSecrets`. |
| `CrashLoopBackOff` | App crashes shortly after start | `kubectl logs --previous <pod>` for the crash, then `describe` for exit codes. Don't keep restarting — fix the manifest and `apply`. |
| `OOMKilled` (exit 137) | Container hit its memory limit | Raise `resources.limits.memory`, or fix the leak. `kubectl top pod` shows steady-state usage. |
| `kubectl exec` returns immediately with no error | Container has no shell (distroless / scratch) | Use `kubectl debug <pod> --image=busybox --target=<container>` for an ephemeral debug sidecar instead. |
| `Service` works but pods can't reach each other | NetworkPolicy denies the traffic | `kubectl get networkpolicy -A`. Either widen the policy or test the path with `kubectl debug` from inside the namespace. |
| `kubectl apply` says nothing changed but the cluster is wrong | The field is owned by another controller (HPA, mutating webhook) | `kubectl get <obj> -o yaml \| grep -A5 managedFields`. Apply the change via the right controller, or use `--force --field-manager`. |
| Rollout stuck halfway | New pods failing readiness, so old pods never get cleaned up | `kubectl rollout status` shows it; `kubectl describe deployment` shows conditions. Fix readiness probe or roll back with `kubectl rollout undo`. |
| `error: metrics not available` from `kubectl top` | metrics-server is not installed | Install metrics-server via your cluster's add-on manager. Without it, no `top`, no HPA. |

---

## Companion: where kubectl fits in a real deploy pipeline

kubectl is excellent for direct cluster interaction — debugging a pod, flipping a Service selector during a cutover, rolling back a bad release. For day-to-day deploys, you want all of that wrapped in a pipeline that runs the same commands the same way every time, from the same source of truth.

A typical containerised deploy looks like this:

1. Push to your `main` branch on GitHub or GitLab.
2. Your CI builds a SHA-tagged image and pushes to your registry.
3. A [DeployHQ build pipeline](https://www.deployhq.com/features/build-pipelines) renders your Kustomize overlay with the new SHA, then runs `kubectl diff -f` and `kubectl apply -f` against the production context.
4. `kubectl rollout status` gates the release — on failure, the same pipeline runs `kubectl rollout undo` and reports a red deploy.
5. The exact same release is pinned by SHA, so [one-click rollback](https://www.deployhq.com/features/one-click-rollback) brings back the previous revision instantly.

Kubernetes isn't the only target — DeployHQ also ships plain SCP/SFTP releases, Docker workloads, and serverless functions through the same pipeline. The principle is the same: Git is the source of truth, the pipeline applies it, the cluster (or server) reflects it. The [deploy from GitHub to your server](https://www.deployhq.com/deploy-from-github) guide walks through the GitHub side end-to-end. For the broader pattern across both Kubernetes and non-Kubernetes targets, [What Is GitOps?](https://www.deployhq.com/blog/what-is-gitops) covers the principles.

---

## Related cheatsheets

- [Docker cheatsheet](https://www.deployhq.com/cheatsheets/docker) — the runtime under every pod; build the image before you deploy it.
- [Bash scripting cheatsheet](https://www.deployhq.com/cheatsheets/bash) — for the deploy hooks that wrap `kubectl diff && kubectl apply && kubectl rollout status` with proper error handling.
- [SSH cheatsheet](https://www.deployhq.com/cheatsheets/ssh) — for bastion access to clusters that aren't exposed to the public internet.
- [AWS CLI cheatsheet](https://www.deployhq.com/cheatsheets/aws-cli) — for the `aws eks update-kubeconfig`, ECR login, and CloudFormation steps that bracket every EKS deploy.

---

## Ship Kubernetes workloads with DeployHQ

DeployHQ runs your `kubectl` pipeline from Git on every push — build, render, diff, apply, verify, rollback — with the same build pipelines and atomic releases used for non-Kubernetes targets. [Start a free trial](https://www.deployhq.com/signup) or read the [pricing tiers](https://www.deployhq.com/pricing).

Need help? Email [support@deployhq.com](mailto:support@deployhq.com) or follow us on [@deployhq on X](https://x.com/deployhq).