kubectl Basics

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

How kubectl talks to the cluster, imperative vs declarative workflows, apply vs create, output formatting, and the commands you will use every day.

kubectl is a REST client. Every command turns into an HTTP request to the API server, authenticated with credentials from your kubeconfig.

Internalising that explains a lot. Every kubectl verb maps to an HTTP method, anything you can do in a manifest you can do through the API, and permission errors are RBAC decisions, not client bugs.

Request flow: kubectl → running pod

kubectl apply

Your command

API Server

Validates & persists to etcd

etcd

Stores desired state

Scheduler

Picks the best node

kubelet

Receives pod spec

Container Runtime

Pulls image & starts container

Pod Running

✓ Desired state achieved

Where it gets its configuration

kubectl config current-context     # which cluster am I pointed at?
kubectl config get-contexts        # all of them
kubectl config use-context prod    # switch

~/.kube/config holds clusters, users and contexts. A context pairs a cluster with credentials and a default namespace.

Check your context before running anything destructive. Running a command against the wrong cluster is the most common serious mistake people make with Kubernetes, and nothing in the output warns you. Tools like kubectx, or a shell prompt showing the current context, are worth the five minutes to set up.

# See exactly what is being sent and where
kubectl get pods -v=6

Imperative and declarative

Two ways to work, and the distinction matters more than it first appears.

Imperative: tell the cluster to do something now:

kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3
kubectl expose deployment web --port=80

Fast, good for learning and debugging, and untracked. Nothing records why the cluster is in this state, and nobody can reproduce it.

Declarative: write the desired state in a file, apply it:

kubectl apply -f deployment.yaml

The file is reviewable, diffable, version-controlled, and reproducible. This is how anything real should be managed.

The productive middle path is to generate manifests imperatively and then keep them:

kubectl create deployment web --image=nginx:1.27 \
  --dry-run=client -o yaml > deployment.yaml

That gives you a valid skeleton in seconds without memorising the schema. Edit it, commit it, apply it.

apply vs create vs replace

CommandBehaviourUse when
createFails if the object existsOne-off creation
applyCreates or updates; merges changesNearly always
replaceOverwrites the whole object; fails if absentRare
patchChanges specific fieldsScripting a targeted edit

apply performs a three-way merge between the last applied configuration, your file, and the live object. That is why it can update one field without discarding changes made by controllers, and why mixing apply with kubectl edit produces confusing results.

Preview before applying to anything that matters:

kubectl diff -f deployment.yaml
kubectl apply -f deployment.yaml --dry-run=server

--dry-run=server validates against the real API server including admission controllers, so it catches far more than client-side validation.

The commands you will actually run

# What is here?
kubectl get pods
kubectl get pods -o wide                 # + IPs and nodes
kubectl get pods -A                      # every namespace
kubectl get pods -w                      # watch changes live
kubectl get all                          # common resource types in one view

# What is wrong with it?
kubectl describe pod <name>              # spec, status and events. Start here
kubectl logs <name>
kubectl logs <name> --previous           # the run that crashed
kubectl logs <name> -f                   # follow
kubectl logs -l app=web --tail=50        # across all matching Pods

# Get inside
kubectl exec -it <name> -- sh
kubectl port-forward pod/<name> 8080:80

# Change things
kubectl apply -f manifest.yaml
kubectl scale deployment/web --replicas=5
kubectl rollout restart deployment/web
kubectl delete -f manifest.yaml

kubectl describe is the single highest-value command. The Events section at the bottom usually states the problem in plain language. Failed scheduling, image pull errors, probe failures, volume mount problems.

Selecting and filtering

kubectl get pods -l app=web                      # by label
kubectl get pods -l 'env in (staging,prod)'      # set-based
kubectl get pods --field-selector status.phase=Running
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get pods --show-labels

Label selectors are how everything in Kubernetes finds everything else, so they are worth being fluent in. Most commands accept -l, including logs and delete.

Output formats

kubectl get pod web-abc -o yaml           # the full object as stored
kubectl get pod web-abc -o json

# Extract one value
kubectl get pod web-abc -o jsonpath='{.status.podIP}'

# Build a custom table
kubectl get pods -o custom-columns=\
NAME:.metadata.name,\
STATUS:.status.phase,\
NODE:.spec.nodeName,\
RESTARTS:.status.containerStatuses[0].restartCount

Two genuinely useful one-liners:

# Images running in a namespace
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'

# Why did that container die?
kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'

Namespaces

kubectl get pods -n kube-system
kubectl get pods -A

# Stop typing -n on every command
kubectl config set-context --current --namespace=payments

Forgetting the namespace is the most common reason kubectl get pods returns "No resources found" when you know something is running.

Making it fast

# Shell completion. Do this once, it pays for itself immediately
source <(kubectl completion bash)     # or zsh
echo 'source <(kubectl completion bash)' >> ~/.bashrc

# The near-universal alias
alias k=kubectl
complete -o default -F __start_kubectl k
kubectl explain pod.spec.containers.resources
kubectl api-resources          # every type, with short names

kubectl explain documents any field from the live API server, which beats searching for documentation that may describe a different version than you are running.

Quick reference

TaskCommand
Current clusterkubectl config current-context
Switch clusterkubectl config use-context <name>
Default namespacekubectl config set-context --current --namespace=<ns>
List with detailkubectl get pods -o wide
Diagnosekubectl describe pod <name>
Logs from a crashkubectl logs <name> --previous
Shell inkubectl exec -it <name> -- sh
Local accesskubectl port-forward svc/<name> 8080:80
Generate a manifestkubectl create deployment x --image=y --dry-run=client -o yaml
Preview a changekubectl diff -f file.yaml
Field documentationkubectl explain <resource>.<field>
Recent eventskubectl get events -A --sort-by=.lastTimestamp

Explore further in this section

Sub-topicWhat you will learn
get and describeInspecting resources and reading events
apply and deleteManaging objects declaratively
exec, logs and port-forwardDebugging running workloads
contexts and namespacesWorking across clusters safely

Key takeaways

  • kubectl is an API client. Permission errors are RBAC decisions; every verb is an HTTP call.
  • Check your context before anything destructive. Nothing warns you that you are on production.
  • Use apply with files in Git, and use imperative commands to generate those files with --dry-run=client -o yaml.
  • kubectl describe first. The Events section names most problems outright.
  • kubectl logs --previous is what you need for a crashed container, not plain logs.
  • kubectl diff -f before applying to anything that matters.
  • Install shell completion and alias k. It pays for itself the same day.

Check your understanding

Exercise

A manifest applied twice

You run kubectl create -f deployment.yaml, edit the replica count in the file, and run the same command again. What happens?

Exercise

No resources found

kubectl get pods returns "No resources found in default namespace" but a colleague confirms the application is running. What is the most likely explanation?


Frequently Asked Questions

What is the difference between kubectl apply and kubectl create?

create maps to an HTTP POST and fails if the object already exists, so it is for one-off creation only. apply creates the object if absent and updates it if present, using a three-way merge between the last applied configuration, your file and the live object. That merge is what lets it change one field without discarding values set by controllers, and it is what makes running the same command repeatedly safe. Use apply with manifests in version control; use create mainly with --dry-run=client -o yaml to generate those manifests.

How do I see why a Pod is failing?

Start with kubectl describe pod <name> and read the Events section at the bottom, which usually states the cause directly. Failed scheduling with the reason, an image pull error with the registry's own message, or a failing probe. If the container is crash-looping, kubectl logs <name> --previous shows output from the run that actually failed; plain kubectl logs shows the instance that has only just started and typically has nothing yet. For the termination reason specifically, kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' returns values like OOMKilled.

How do I switch between clusters and namespaces?

kubectl config get-contexts lists available contexts and kubectl config use-context <name> switches cluster. For namespaces, kubectl config set-context --current --namespace=<ns> changes the default so you stop typing -n on every command. Because nothing in the output tells you which cluster you are on, showing the current context in your shell prompt, or using kubectx and kubens, is a worthwhile safeguard against running something destructive in the wrong place.

How do I generate YAML without writing it by hand?

Use an imperative command with --dry-run=client -o yaml, for example kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > deployment.yaml. Nothing is sent to the cluster and you get a valid manifest to edit and commit. The same trick works for Services, ConfigMaps, Secrets and Jobs, and it is far quicker than recalling the schema. kubectl explain <resource>.<field> documents any field against the version of Kubernetes you are actually running.

Why does kubectl get pods show nothing when my app is running?

Almost always the namespace. kubectl operates on one namespace at a time, defaulting to the one in your current context, so a workload in payments is invisible from default. Run kubectl get pods -A to search everywhere. The other possibility is that you are connected to a different cluster than you think, kubectl config current-context settles that immediately.

What does kubectl diff do?

kubectl diff -f manifest.yaml shows what applying that file would change, comparing your manifest against the live objects in the cluster and printing a unified diff. It is the closest equivalent to terraform plan and is worth running before any change to a production namespace. Pair it with kubectl apply --dry-run=server, which validates against the real API server including admission controllers and catches problems that client-side validation cannot see.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources