Kubernetes Imperative Commands vs Declarative: Powerful Strategies to Master kubectl Commands 2025
Post 10 of 70 in “Mastering Kubernetes: A Practical Journey from Beginner to CKA”
🔥 TL;DR:
- Imperative commands execute immediate actions (kubectl create, run, expose) while declarative uses YAML files (kubectl apply)
- Imperative is perfect for quick tasks and troubleshooting, declarative for production and version control
- Master both approaches – imperative for speed, declarative for reliability
- kubectl apply enables GitOps workflows and infrastructure-as-code practices
- Production teams typically use 80% declarative, 20% imperative commands
Table of Contents
Introduction
Picture this: You’re in a restaurant, and there are two ways to get your meal. You could walk into the kitchen, grab ingredients, tell the chef exactly how to slice the vegetables, specify the cooking temperature, and micromanage every step (imperative). Or you could simply order “the pasta special” from the menu, trusting the kitchen to deliver what you want (declarative). Both get you fed, but they’re completely different approaches.
This same fundamental choice exists in Kubernetes, and honestly, it confused the heck out of me when I started. I’d see tutorials mixing kubectl run commands with kubectl apply, wondered why some people swore by YAML files while others lived in the terminal, and couldn’t figure out when to use what approach.
What we’ll learn today: • The fundamental difference between imperative and declarative operations • When to use kubectl create, run, expose vs kubectl apply • How to convert between approaches seamlessly • Production strategies that combine both methods effectively
Why this matters: Understanding these two paradigms isn’t just academic – it’s the difference between quick-and-dirty fixes and maintainable, scalable infrastructure. Every senior DevOps engineer I know has strong opinions about this balance, and for good reason.
By the end of this post, you’ll confidently choose the right approach for any situation, whether you’re debugging a production issue at 3 AM or setting up a new microservice architecture.
In our previous post, we explored Kubernetes Services and how they expose your applications. Today we’re building on that knowledge to understand the two fundamental ways you can create and manage those Services (and everything else in Kubernetes). This knowledge will transform how you interact with your cluster daily.

Prerequisites
What you need to know:
- Basic kubectl commands (get, describe, delete)
- Understanding of Pods, Services, and Deployments from previous posts
- Familiarity with YAML syntax basics
- Knowledge of how Services work (covered in post #9)
📌 Quick Refresher: Remember how we created Services in the last post? We used both kubectl expose commands and YAML manifests. That’s exactly the imperative vs declarative distinction we’re exploring today!
Tools required:
- Running Kubernetes cluster (minikube, kind, or cloud cluster)
- kubectl configured and connected
- Text editor for YAML files
- Basic understanding of version control (we’ll touch on GitOps)
Previous posts to read:
- Post #9: “Services Demystified: ClusterIP, NodePort, and LoadBalancer”
- Post #8: Kubernetes ReplicaSet vs Deployment: Essential Guide to Choosing the Right One
Estimated time: 45 minutes for reading and hands-on practice
Step-by-Step Tutorial: Kubernetes imperative commands vs declarative
Theory First: The Philosophy Behind Two Approaches
Here’s what I wish someone had explained to me early on: the difference between imperative and declarative isn’t just syntax – it’s two completely different philosophies about managing infrastructure.
Imperative approach is like being a micromanaging boss. You tell Kubernetes exactly what to do, step by step: “Create this pod, then add these labels, now expose it with a service, configure this port…” You’re giving direct orders.
Declarative approach is like being a visionary leader. You describe the end state you want: “Here’s a complete configuration file showing how my application should look when running.” Kubernetes figures out the steps to make it happen.

The beauty of declarative management is idempotency – you can run the same command multiple times safely. It’s like saying “make sure my application looks like this” rather than “create my application.” If it already exists and matches your spec, Kubernetes says “already done!” If it needs changes, it makes them.
Think of it like GPS navigation. Imperative is turn-by-turn directions: “Turn left in 100 feet, then right at the stop sign.” Declarative is just the destination: “Get me to 123 Main Street” – the GPS (Kubernetes) figures out the best route.
Hands-on Implementation
Let’s see these approaches in action by creating the same nginx deployment using both methods.
Step 1: The Imperative Way
First, let’s create a deployment the imperative way:
# Create a deployment imperatively
kubectl create deployment nginx-imperative --image=nginx:1.24
# Scale it up
kubectl scale deployment nginx-imperative --replicas=3
# Expose it as a service
kubectl expose deployment nginx-imperative --port=80 --type=ClusterIP
# Add some labels
kubectl label deployment nginx-imperative environment=demo
kubectl label deployment nginx-imperative team=platform
Expected output:
deployment.apps/nginx-imperative created
deployment.apps/nginx-imperative scaled
service/nginx-imperative exposed
deployment.apps/nginx-imperative labeled
deployment.apps/nginx-imperative labeled
Verification step:
kubectl get deployment nginx-imperative -o wide
kubectl describe service nginx-imperative
Notice how we built this step by step, each command doing one specific thing. This is perfect for quick experiments or one-off tasks.
Step 2: The Declarative Way
Now let’s create the exact same setup declaratively. Create a file called nginx-declarative.yaml:
# nginx-declarative.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-declarative
labels:
app: nginx-declarative
environment: demo
team: platform
spec:
replicas: 3
selector:
matchLabels:
app: nginx-declarative
template:
metadata:
labels:
app: nginx-declarative
spec:
containers:
- name: nginx
image: nginx:1.24
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-declarative
labels:
environment: demo
team: platform
spec:
selector:
app: nginx-declarative
ports:
- port: 80
targetPort: 80
type: ClusterIP
Apply it with:
kubectl apply -f nginx-declarative.yaml
Expected output:
deployment.apps/nginx-declarative created
service/nginx-declarative created
Verification step:
kubectl get deployment nginx-declarative -o wide
kubectl describe service nginx-declarative
❓ Check Understanding: Run both verification commands. Do you see any differences between the imperative and declarative deployments? They should look nearly identical!
Step 3: Testing Idempotency
Here’s where declarative really shines. Run the apply command again:
kubectl apply -f nginx-declarative.yaml
Expected output:
deployment.apps/nginx-declarative unchanged
service/nginx-declarative unchanged
See that “unchanged”? That’s idempotency in action. The declarative approach compared your desired state with the current state and said, “Already good!”
Now try running one of the imperative commands again:
kubectl create deployment nginx-imperative --image=nginx:1.24
Expected output:
error: failed to create deployment: deployments.apps "nginx-imperative" already exists
The imperative approach doesn’t handle this gracefully – it tries to create something that already exists.
Step 4: Making Changes
Let’s see how updates work. For the imperative deployment:
# Change the image version
kubectl set image deployment/nginx-imperative nginx=nginx:1.25
# Scale it differently
kubectl scale deployment nginx-imperative --replicas=5
For the declarative deployment, edit your YAML file:
# In nginx-declarative.yaml, change these lines:
replicas: 5 # Changed from 3
# ... and in the container spec:
image: nginx:1.25 # Changed from nginx:1.24
Then apply:
kubectl apply -f nginx-declarative.yaml
Expected output:
deployment.apps/nginx-declarative configured
service/nginx-declarative unchanged
💡 Pro Tip: Notice how the service was “unchanged” but the deployment was “configured”? kubectl apply only updates what actually changed. It’s incredibly efficient!
Step 5: The Hybrid Approach
Here’s a real-world secret: most experienced Kubernetes users combine both approaches strategically. Let me show you how.
Use imperative commands to generate declarative templates:
# Generate YAML without creating anything
kubectl create deployment sample-app --image=nginx:1.24 --dry-run=client -o yaml > sample-app.yaml
# Generate a service YAML
kubectl expose deployment sample-app --port=80 --dry-run=client -o yaml > sample-service.yaml
This gives you the speed of imperative thinking with the benefits of declarative management. You can quickly generate templates, then customize and version control them.
Critical Production Insights
⚠️ Production Alert: Never use imperative commands directly on production clusters unless it’s an emergency. Every change should go through your CI/CD pipeline with declarative manifests. I learned this the hard way when an urgent “quick fix” with kubectl edit caused a outage because it bypassed our review process.
💡 Pro Tip: Keep a cheat sheet of imperative commands for troubleshooting. When your application is down at 2 AM, you want to quickly spin up a debug pod with kubectl run debug --image=busybox -it --rm -- sh, not hunt around for a YAML file.
⚠️ Warning: Be careful with kubectl replace – it deletes and recreates resources, causing downtime and losing any rolling update benefits. Unlike kubectl apply which triggers graceful rolling updates for Deployments, kubectl replace brutally stops all pods before creating new ones. Always prefer kubectl apply unless you specifically need replace semantics.
Resource Management Best Practices:
- Use resource requests and limits in your YAML files:
resources: {requests: {memory: "64Mi", cpu: "250m"}, limits: {memory: "128Mi", cpu: "500m"}} - Always include health checks:
livenessProbeandreadinessProbe - Use meaningful labels and annotations for organization and monitoring
Real-World Scenarios
Scenario 1: E-commerce Platform with 50+ Microservices
At my last company, we ran a major e-commerce platform with over 50 microservices. Here’s how we balanced imperative and declarative approaches:
- 100% declarative for deployments: All application deployments used GitOps with ArgoCD
- Imperative for debugging: When a service went down, we’d quickly spin up debug pods:
kubectl run debug-payment --image=curlimages/curl -it --rm -- sh - Declarative for infrastructure: All supporting infrastructure (monitoring, logging, ingress controllers) managed through Helm charts
- Imperative for emergencies: Emergency scaling during Black Friday:
kubectl scale deployment payment-service --replicas=20
The result? We deployed 15+ times per day with zero-downtime deployments, but could still respond to incidents in seconds.
Scenario 2: Development Team Workflow
Here’s a typical workflow that combines both approaches effectively:
- Local development (mostly imperative):
kubectl run test-pod --image=my-app:dev -it --rm kubectl port-forward deployment/api-service 8080:8080 - Creating new features (hybrid):
# Generate template kubectl create deployment new-feature --image=my-app:v2.1 --dry-run=client -o yaml > feature.yaml # Customize, then apply kubectl apply -f feature.yaml - Production deployment (declarative only): All changes go through GitOps pipeline with proper reviews.
Industry Examples
Netflix’s approach: They use a primarily declarative approach with their Spinnaker deployment system, but operations teams have imperative override capabilities for incident response. Their SREs can quickly scale services during traffic spikes using imperative commands while maintaining declarative definitions for normal operations.
Spotify’s strategy: They’ve built internal tools that generate declarative manifests from higher-level abstractions, giving developers the simplicity of imperative thinking with the reliability of declarative management. Their platform team maintains the templates, while product teams just specify what they need.
Common Mistakes to Avoid
Mistake #1: Using kubectl edit in production
# Don't do this in production!
kubectl edit deployment critical-app
Why it’s bad: Changes aren’t version controlled, can’t be reviewed, and will be overwritten by your next GitOps deployment.
Better approach: Update your manifests and apply them:
# Edit the YAML file, then:
kubectl apply -f updated-manifests/
Mistake #2: Mixed imperative/declarative management Don’t create a resource imperatively and then try to manage it declaratively – you’ll get conflicts and confusing behavior.
Mistake #3: Not using labels consistently Both approaches need good labeling strategies. Without consistent labels, you can’t effectively query, monitor, or manage your resources.
Troubleshooting Tips
Common Error 1: “Resource Already Exists”
Issue: Trying to create a resource that already exists with imperative commands.
error: failed to create deployment: deployments.apps "my-app" already exists
Solution:
# Check what exists first
kubectl get deployment my-app
# If you want to replace it completely
kubectl replace --force -f my-deployment.yaml
# If you want to update it
kubectl apply -f my-deployment.yaml
# Or delete and recreate
kubectl delete deployment my-app
kubectl create -f my-deployment.yaml
Common Error 2: “Field is Immutable”
Issue: Trying to change immutable fields in existing resources.
error: field selector is immutable
Solution: Some fields can’t be changed after creation. For Deployments, the good news is that kubectl apply often handles this gracefully – it will trigger a rolling update that recreates pods with the new configuration. For other resources or more complex changes, you may need to delete and recreate:
# For most Deployment changes, just apply - it handles recreation automatically
kubectl apply -f my-updated-deployment.yaml
# For resources that truly need recreation, save current resource first
kubectl get deployment my-app -o yaml > my-app-backup.yaml
# Clean up the YAML (remove status, metadata.generation, etc.)
# You can install kubectl-neat plugin: go install github.com/itaysk/kubectl-neat@latest
kubectl get deployment my-app -o yaml | kubectl neat > my-app-backup.yaml
# Or manually clean with grep/sed (remove status and generated metadata fields)
kubectl get deployment my-app -o yaml | grep -v -E "^\s*(status:|resourceVersion:|uid:|creationTimestamp:|generation:)" > clean-backup.yaml
# Delete and recreate
kubectl delete deployment my-app
kubectl apply -f my-updated-deployment.yaml
Common Error 3: Conflicts Between Management Methods
Issue: Resource was created imperatively but you’re trying to apply declarative changes.
Solution: Adopt the resource into declarative management:
# Add the annotation that kubectl apply uses
kubectl annotate deployment my-app kubectl.kubernetes.io/last-applied-configuration='{}'
# Now you can use kubectl apply
kubectl apply -f my-deployment.yaml
Debug Commands for Both Approaches
# See how a resource was created
kubectl describe deployment my-app
# Check for management annotations
kubectl get deployment my-app -o jsonpath='{.metadata.annotations}'
# Compare desired vs actual state
kubectl diff -f my-manifest.yaml
# Watch changes in real-time
kubectl get pods -w
# Get detailed resource definitions
kubectl get deployment my-app -o yaml
Health Checks and Validation
# Validate YAML syntax before applying
kubectl apply --dry-run=client -f my-manifest.yaml
# Test connectivity to services
kubectl run test-pod --image=curlimages/curl -it --rm -- curl my-service:80
# Check resource quotas and limits
kubectl describe resourcequota
kubectl top pods
Where to Get Help
- Official Kubernetes docs: kubernetes.io/docs/concepts/overview/working-with-objects/
- kubectl cheat sheet: Essential for both imperative and declarative patterns
- Community Slack: #kubectl channel in Kubernetes Slack
- Stack Overflow: Tag your questions with both [kubernetes] and [kubectl]
💡 Advanced Reading: Once you’re comfortable with client-side apply, explore kubectl apply --server-side for handling complex field ownership conflicts in multi-operator environments.
Next Steps
Here’s what’s coming next in our journey, and trust me, it builds perfectly on today’s foundation: “Labels and Selectors: The Kubernetes Taxonomy System” (Post #11).
Why should you be excited? Because labels are the secret sauce that makes both imperative and declarative approaches truly powerful. Every kubectl get query, every service selector, every deployment update strategy relies on labels. You’ll discover how to build elegant resource taxonomies that make your cluster self-documenting and incredibly manageable.
Additional learning paths to explore:
- Dive deeper into GitOps workflows with ArgoCD or Flux
- Explore Kubernetes manifests in Helm charts
- Learn about Kustomize for template-free customization
Practice challenges:
🔧 Beginner Challenge: Convert an existing imperative deployment to declarative management. Create a simple app imperatively, then export it to YAML and manage it declaratively.
🔧 Intermediate Challenge: Build a multi-tier application (frontend, backend, database) using pure declarative manifests. Include proper resource limits, health checks, and labels.
🔧 Advanced Challenge: Create a “bootstrap” script that uses imperative commands to generate a complete set of declarative manifests for a microservices architecture, including services, deployments, configmaps, and secrets.
Community engagement: Share your hybrid workflows! What’s your favorite combination of imperative and declarative commands? Join the discussion in the comments – I’m always curious how different teams balance these approaches.
💡 Advanced Reading: Once you’re comfortable with client-side apply, explore kubectl apply --server-side for handling complex field ownership conflicts in multi-operator environments.
Conclusion
The imperative vs declarative choice in Kubernetes isn’t really a choice at all – it’s about using the right tool for the right job. After working with both approaches extensively, I’ve come to see them as complementary superpowers rather than competing philosophies.
The key insight: Imperative commands are your debugging Swiss Army knife – perfect for quick investigations, emergency fixes, and rapid prototyping. Declarative manifests are your production backbone – reliable, versionable, and scalable for everything that matters to your business.
The teams I’ve worked with that truly excel at Kubernetes embrace this duality. They prototype quickly with imperative commands, generate templates with hybrid approaches, then lock in reliability with declarative manifests. They never feel guilty about running kubectl port-forward during debugging, but they’d never dream of scaling production services without proper GitOps workflows.
What you’ve gained today:
- Understanding of when each approach shines
- Practical skills for both imperative speed and declarative reliability
- Production-ready strategies that combine both methods effectively
- Troubleshooting skills for the inevitable conflicts and edge cases
As we move into more advanced Kubernetes topics, you’ll see these patterns everywhere. Services, ConfigMaps, Secrets, RBAC – every resource type benefits from this imperative/declarative understanding. The labels and selectors we’re covering next will multiply the power of both approaches exponentially.
FAQ’s: Kubernetes imperative commands vs declarative
When should I use imperative vs declarative commands in Kubernetes?
Use imperative commands for quick tasks, troubleshooting, and one-time operations like kubectl run debug-pod --image=busybox -it --rm. Use declarative manifests with kubectl apply for anything that needs to be repeatable, version controlled, or managed in production. The rule of thumb: if you might need to do it again, make it declarative.
What happens when I mix imperative and declarative management on the same resource?
Mixing management methods can cause conflicts and unpredictable behavior. kubectl apply tracks changes through the kubectl.kubernetes.io/last-applied-configuration annotation, but imperative commands don’t update this annotation. This means kubectl apply only knows about changes made through previous apply commands, not imperative modifications. When you run the next apply, it might revert imperative changes because they’re not reflected in the tracked configuration state. Choose one approach per resource and stick with it.
Can I convert an imperatively created resource to declarative management?
Yes! Export the resource using kubectl get resource-name -o yaml > resource.yaml, clean up the output by removing status and metadata fields, then use kubectl apply -f resource.yaml. You might need to add the kubectl.kubernetes.io/last-applied-configuration annotation for smooth management.
Why does kubectl apply sometimes say “unchanged” even when I modified my YAML file?
kubectl apply uses a three-way merge comparing your file, the last-applied-configuration annotation, and the current cluster state. If the effective changes result in the same final state, it reports “unchanged.” This is actually good – it means your cluster already matches your desired state.
What’s the difference between kubectl create and kubectl apply?
kubectl create is imperative – it fails if the resource already exists and doesn’t handle updates. kubectl apply is declarative – it creates resources if they don’t exist, updates them if they do, and only changes what’s actually different. Always prefer kubectl apply for production workflows.
