Two Ways to Tell Kubernetes What You Want
There are two fundamentally different ways to communicate with Kubernetes. The distinction is the same one that separates a recipe from an order at a restaurant:
- Recipe — "Boil water, add tea powder, add sugar, add milk, boil for 5 minutes, filter." Step-by-step instructions.
- Order — "I want one cup of tea." A statement of the desired outcome; someone else figures out the steps.
Both get you tea. The difference is how you communicate the request. Kubernetes supports both models — the imperative approach (commands) and the declarative approach (configuration files) — and knowing when to reach for each is one of the more practical skills in day-to-day cluster work.
Imperative: Telling Kubernetes How
In the imperative model, you tell Kubernetes exactly how to perform a task by issuing direct kubectl commands. It's the same mental model as running docker run nginx — you're giving an explicit, one-shot instruction: run a container, use the nginx image, start it now.
kubectl run nginx --image=nginxA handful of common imperative commands:
# Create a Pod
kubectl run nginx --image=nginx
# Create a namespace
kubectl create namespace dev
# Delete a Pod
kubectl delete pod nginx
# Scale a Deployment
kubectl scale deployment nginx --replicas=5
# Expose a Deployment as a Service
kubectl expose deployment nginx --port=80 --type=NodePortEach command flows straight from developer to Kubernetes with nothing persisted in between:
Developer → Command → Kubernetes
Imperative commands are fast, easy to type, and require no files — which makes them genuinely good for learning the API, spinning up throwaway Pods, debugging a live cluster, or running quick experiments.
Where imperative breaks down
The trouble shows up once a real application enters the picture. Say a Pod created imperatively gets deleted — recreating it means remembering the exact kubectl run invocation that made it in the first place. Now multiply that by a realistic application stack: a Deployment, a Service, an Ingress, a ConfigMap, a Secret, a PVC. Reproducing that from memory as a sequence of commands isn't practical, and there's no way to hand it off to a teammate short of writing the commands down somewhere.
That "somewhere" is the real problem: imperative commands aren't a durable artifact. They can't be checked into Git, diffed, reviewed in a pull request, or rolled back. Once the terminal history scrolls away, so does the only record of what was done. That's what makes imperative unsuitable as the primary way to manage anything beyond small, temporary, or exploratory work.
Declarative: Telling Kubernetes What
The declarative model flips the responsibility. You describe what you want the end state to look like, and Kubernetes works out how to get there. Instead of a command, you write a YAML manifest:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginxThen hand that file to Kubernetes:
kubectl apply -f pod.yamlThe flow now has a durable artifact sitting between the developer and the cluster:
Developer → YAML File → Kubernetes → Application
This is the same idea as an architect handing over a blueprint instead of directing bricklayers one brick at a time, or ordering "one Veg Biryani" instead of narrating each step of the recipe — you specify the target, and the system (Kubernetes, the builders, the chef) handles execution.
For a Deployment specifically, applying a manifest triggers a chain of objects rather than a single resource:
Developer → deployment.yaml → kubectl apply → API Server → Deployment → ReplicaSet → Pods
Why YAML? Infrastructure as Code
Commands are ephemeral — once run, they leave no trace beyond whatever is running in the cluster. YAML files don't have that problem: the same deployment.yaml that describes today's state describes tomorrow's, and next year's, and can be applied to any cluster that needs it.
Storing infrastructure definitions as files rather than as one-off commands is the practice known as Infrastructure as Code (IaC). Instead of running:
kubectl create namespace dev
kubectl run nginx
kubectl expose deployment nginxyou store the equivalent state as files:
namespace.yaml
deployment.yaml
service.yaml
ingress.yaml
and commit them to Git. That single change — files instead of commands — unlocks everything a normal software workflow expects:
- Reusable across environments and clusters
- Version controlled with full history
- Easy to share with a team
- Easy to review through a pull request
- Easy to modify — edit the file, reapply
Git + YAML: The Production Workflow
Once manifests live in Git, deployment stops being a manual, one-off act and becomes a pipeline:
Developer → Git Repository → deployment.yaml → CI/CD → Kubernetes
In a mature setup, developers never SSH into a production cluster and type kubectl run nginx. They write manifests, push to Git, and let CI/CD apply the change — which makes every deployment repeatable, reviewable, and auditable.
Before applying a change you're unsure about, run kubectl diff -f deployment.yaml. It shows exactly what will change on the cluster without touching anything — much safer than apply-ing blind and checking afterward.
Imperative vs Declarative at a Glance
| Imperative | Declarative |
|---|---|
| Commands | YAML files |
| Tells Kubernetes HOW | Tells Kubernetes WHAT |
| Manual | Automatable |
| Difficult to reproduce | Easy to reproduce |
| Not version controlled | Version controlled |
| Temporary | Permanent |
| Good for testing | Best for production |
Same result, two approaches
Both of these produce an identical Pod — the only difference is whether the instruction is a command or a file:
# Imperative
kubectl run nginx --image=nginx# Declarative — pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginxkubectl apply -f pod.yamlWhen to Use Which
| Use imperative for | Use declarative for |
|---|---|
| Learning Kubernetes | Production workloads |
| Temporary Pods | DevOps and CI/CD pipelines |
| Live debugging | Team collaboration |
| Quick experiments | Large, multi-resource applications |
| Interview practice | Anything that needs Git history |
kubectl apply isn't purely declarative bookkeeping — it actually reconciles state. Re-running kubectl apply -f pod.yaml after editing the image tag doesn't error out or duplicate anything; Kubernetes diffs the YAML against the live object and updates only what changed.
Key Takeaways
Imperative commands are fast and simple, which makes them the right tool for learning and for testing — but they're inherently disposable and don't scale past a handful of resources. Declarative YAML trades a little upfront ceremony for a durable, versioned, reviewable description of desired state, which is why it's the default for anything running in production.
A useful memory trick: Imperative = HOW (commands), Declarative = WHAT (YAML). In practice, you'll spend most of your real-world Kubernetes time writing YAML files and running kubectl apply.