πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Configuration beginner ⏱ 8 minutes K8s 1.28+

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.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: kubectl apply -f file.yaml is declarative β€” it creates resources if they don’t exist and updates them if they do, tracking changes via the last-applied-configuration annotation. kubectl create -f file.yaml is imperative β€” it creates resources but fails if they already exist. Use apply for GitOps, CI/CD, and production. Use create for 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

Featurekubectl applykubectl create
Creates new resourcesβœ…βœ…
Updates existingβœ… (merge patch)❌ (error)
Deletes removed fieldsOnly with --pruneN/A
Tracks changeslast-applied-configurationNo 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-conflicts

How 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:

  1. Last applied (annotation) vs current live vs new desired
  2. Fields you added β†’ created
  3. Fields you changed β†’ updated
  4. Fields you removed from YAML β†’ deleted (if they were in last-applied)
  5. 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.yaml

When 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 --force

Server-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 managedFields

Common 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 apply for everything in production β€” idempotent and GitOps-friendly
  • Use create --dry-run=client -o yaml for generating YAML templates
  • Prefer --server-side on K8s 1.22+ for better conflict handling
  • Never mix create and apply on the same resource β€” causes annotation confusion
  • Store manifests in git β€” kubectl apply + git = GitOps

Key Takeaways

  • kubectl apply is declarative: creates, updates, and is idempotent
  • kubectl create is imperative: creates only, fails if resource exists
  • Apply tracks changes via last-applied-configuration annotation
  • Server-side apply (--server-side) is the modern best practice
  • Use create --dry-run=client -o yaml for YAML generation, apply for everything else
#kubectl #configuration #gitops #cka
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens