Right, let’s settle this. You’re going to hear a lot of dogma about Kubernetes: “Thou shalt only use declarative YAML!” It’s a nice ideal, but the real world is messy. You’re not a bad person for running an imperative command. The trick is knowing when to break the rules. Think of it like this: imperative is for a quick chat, declarative is for a binding contract.

The Quick and Dirty: Imperative Commands

Imperative commands are you telling the API server exactly what to do, right now. Create this! Delete that! They’re the equivalent of shouting orders into a walkie-talkie. Fantastic for exploration, quick fixes, and one-off tasks where you just need to get something done.

Need to quickly see what a LoadBalancer service would look like without actually committing to it? kubectl create service loadbalancer my-app --tcp=80:8080 --dry-run=client -o yaml. Boom. There’s your template. Steal it and walk away.

The real magic, and where imperative commands are genuinely irreplaceable, is in troubleshooting. Your pod is stuck in ImagePullBackOff and you need to see what’s actually in its spec right now. You’re not going to kubectl apply a whole new manifest. You’re going to patch it on the fly:

# Let's quickly add a debug container to a running pod
kubectl debug my-miserable-pod -it --image=busybox --target=my-miserable-pod

Or maybe you need to force a deployment to re-pull an image because you just pushed my-app:latest again (a questionable choice, but we’ve all been there):

# The classic "shake the deployment until it does what you want" command
kubectl patch deployment my-app -p '{"spec":{"template":{"spec":{"containers":[{"name":"my-app","env":[{"name":"LAST_RESTART","value":"'$(date +%s)'"}]}]}}}}'

We’re literally patching in a timestamp environment variable to force a rollout. It’s a hack. It’s imperative. And it saves your afternoon.

The Pitfall: The big, glaring problem with imperative commands is that they create a “mystery state.” There’s no record of your shouted order. If your cluster explodes and you need to rebuild, that one-off kubectl run command you used to create a job three weeks ago is gone forever. You’re left trying to reverse-engineer it from the current state, which is a fool’s errand.

The Source of Truth: Declarative Manifests

This is the Kubernetes way. You don’t shout orders; you write a manifest file that describes your desired state. You hand this contract to Kubernetes and say, “Make it so.” The system’s controllers then compare your desired state with the actual state and relentlessly work to converge the two.

This is powerful because your YAML files become your documentation, your version control history, and your recovery plan. They are the single source of truth.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-registry.com/my-app:v1.2.3 # <- Specific, immutable tag. Good.
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"

You apply it:

kubectl apply -f deployment.yaml

The beauty of apply is that it’s idempotent. You can run it a thousand times, and the result is the same. Change the replicas count to 5 and apply again? Kubernetes will see the diff and simply scale the deployment. It’s managing drift for you.

Why apply is Black Magic (and Why You Should Care)

You’ve probably noticed that when you apply a manifest, Kubernetes adds a bunch of annotations to your live object. This isn’t graffiti; it’s the secret sauce. It stores the last-applied configuration (kubectl.kubernetes.io/last-applied-configuration). This is how kubectl can perform a three-way diff between what you just gave it, what you gave it last time, and what actually exists in the cluster to calculate the precise patch it needs to make.

The Pitfall: Never, ever use kubectl create on a resource that is managed by kubectl apply. You’ll stomp all over these annotations and the next time you apply, the diff calculation will be completely wrong, leading to bizarre and unexpected results. Pick a workflow and stick with it.

The Hybrid Approach: Imperative for the “What,” Declarative for the “Why”

Here’s the pro move. Use imperative commands to generate the what (the boilerplate YAML), but then you own it, version it, and manage it declaratively.

# Generate a perfect starting point for a Deployment manifest
kubectl create deployment my-app --image=nginx:1.23 --dry-run=client -o yaml > deployment.yaml

Now you have a solid template. Open deployment.yaml, add your resource limits, probes, labels, and all the other important stuff—the why behind your configuration. Now you can commit this to git and kubectl apply -f deployment.yaml forevermore. You used an imperative command to avoid carpal tunnel syndrome from typing boilerplate, but you’re firmly in the declarative world.

So, the rule of thumb: If it’s for production, it goes in a version-controlled YAML file. If it’s for a one-off debug session, a quick test, or generating a template, imperative commands are your brilliant, fast, and slightly dangerous friend. Use both. Just know which one you’re talking to.