kubectl apply vs create: Key Differences
Understand when to use kubectl apply vs kubectl create. Declarative vs imperative, last-applied annotation, server-side apply, and GitOps workflows.
π‘ Quick Answer:
kubectl apply -f file.yamlis declarative β it creates resources if they donβt exist and updates them if they do, tracking changes via thelast-applied-configurationannotation.kubectl create -f file.yamlis imperative β it creates resources but fails if they already exist. Useapplyfor GitOps, CI/CD, and production. Usecreatefor one-off resources and CKA exam speed.
The Problem
Both commands create resources, but behave differently:
# First run: both succeed
kubectl create -f deployment.yaml # β
Created
kubectl apply -f deployment.yaml # β
Created
# Second run: different behavior
kubectl create -f deployment.yaml # β Error: already exists
kubectl apply -f deployment.yaml # β
Updated (no error)The Solution
Comparison Table
| Feature | kubectl apply | kubectl create |
|---|---|---|
| Creates new resources | β | β |
| Updates existing | β (merge patch) | β (error) |
| Deletes removed fields | Only with --prune | N/A |
| Tracks changes | last-applied-configuration | No tracking |
| Idempotent | β | β |
| GitOps compatible | β | β |
| Server-side apply | β
--server-side | β |
| Generates YAML | β | β
--dry-run=client -o yaml |
kubectl apply (Declarative)
# Create or update resources
kubectl apply -f deployment.yaml
# Apply entire directory
kubectl apply -f manifests/
# Apply from URL
kubectl apply -f https://raw.githubusercontent.com/example/manifests/main/deploy.yaml
# Apply with prune (delete resources no longer in files)
kubectl apply -f manifests/ --prune -l app=myapp
# Server-side apply (K8s 1.22+, recommended)
kubectl apply -f deployment.yaml --server-side
# Force conflicts (take ownership of fields)
kubectl apply -f deployment.yaml --server-side --force-conflictsHow apply tracks changes:
# After kubectl apply, K8s stores this annotation:
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment",...}When you run apply again, it computes a 3-way merge:
- Last applied (annotation) vs current live vs new desired
- Fields you added β created
- Fields you changed β updated
- Fields you removed from YAML β deleted (if they were in last-applied)
- Fields changed by others (HPA, controllers) β preserved
kubectl create (Imperative)
# Create resources (fails if exists)
kubectl create -f deployment.yaml
# Create with --save-config (adds last-applied annotation)
kubectl create -f deployment.yaml --save-config
# Imperative resource creation
kubectl create deployment nginx --image=nginx:1.27 --replicas=3
kubectl create service clusterip nginx --tcp=80:80
kubectl create configmap myconfig --from-file=config.properties
kubectl create secret generic mysecret --from-literal=password=s3cr3t
kubectl create namespace production
kubectl create serviceaccount mysa
# Generate YAML (most useful feature)
kubectl create deployment nginx --image=nginx:1.27 \
--dry-run=client -o yaml > deployment.yamlWhen to Use Each
# GitOps / CI/CD pipelines β apply
kubectl apply -f manifests/
# Quick testing / CKA exam β create
kubectl create deployment nginx --image=nginx:1.27
# Generate then customize β create + apply
kubectl create deployment nginx --image=nginx:1.27 \
--dry-run=client -o yaml > deployment.yaml
# Edit deployment.yaml...
kubectl apply -f deployment.yaml
# Replace completely (nuclear option)
kubectl replace -f deployment.yaml --forceServer-Side Apply (Modern Best Practice)
# Client-side apply (default, older)
kubectl apply -f deployment.yaml
# Problem: annotation-based, large objects hit annotation size limits
# Server-side apply (recommended for K8s 1.22+)
kubectl apply -f deployment.yaml --server-side
# Benefits:
# - No annotation size limit
# - Field-level ownership tracking
# - Better conflict detection
# - Multiple managers can own different fields
# Check field managers
kubectl get deployment nginx -o yaml | grep -A5 managedFieldsCommon Issues
βalready existsβ with kubectl create
Resource exists. Use kubectl apply instead, or kubectl delete first.
apply warning: βlast-applied-configuration missingβ
Resource was created with kubectl create (no annotation). Fix: kubectl apply -f file.yaml --force or add --save-config to create.
apply deleting fields set by HPA or controllers
Use --server-side apply β it tracks field ownership and wonβt overwrite fields managed by other controllers.
Best Practices
- Use
applyfor everything in production β idempotent and GitOps-friendly - Use
create --dry-run=client -o yamlfor generating YAML templates - Prefer
--server-sideon K8s 1.22+ for better conflict handling - Never mix
createandapplyon the same resource β causes annotation confusion - Store manifests in git β
kubectl apply+ git = GitOps
Key Takeaways
kubectl applyis declarative: creates, updates, and is idempotentkubectl createis imperative: creates only, fails if resource exists- Apply tracks changes via
last-applied-configurationannotation - Server-side apply (
--server-side) is the modern best practice - Use
create --dry-run=client -o yamlfor YAML generation,applyfor everything else

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
