kubectl apply vs kubectl create

Key Differences in Kubernetes

kubectl apply is declarative — it creates or updates resources by comparing the desired state in your YAML file with the current state in the cluster. kubectl create is imperative — it creates a resource and fails if it already exists. Use apply for production workflows and GitOps; use create for quick one-off resource creation.

Side-by-Side Comparison

Dimensionkubectl applykubectl create
ApproachDeclarative — 'make it look like this YAML'Imperative — 'create this resource now'
Existing ResourcesUpdates the resource if it already existsFails with AlreadyExists error if the resource exists
State TrackingStores last-applied-configuration annotation for three-way mergesNo last-applied-configuration tracking
IdempotencyIdempotent — safe to run multiple timesNot idempotent — fails on second run
Use CaseProduction workflows, CI/CD pipelines, GitOpsQuick resource creation, one-off tasks, scripting
Field OwnershipTracks field ownership with server-side applyNo field ownership tracking
Partial UpdatesMerges changes — only modifies fields that differNot applicable — creates the full resource or fails

Detailed Breakdown

Imperative vs Declarative

The fundamental difference is the management model:

# Imperative — "create this deployment with these parameters"
kubectl create deployment web --image=nginx:latest --replicas=3

# Declarative — "make the cluster match this file"
kubectl apply -f deployment.yaml

The imperative approach tells Kubernetes what to do. The declarative approach tells Kubernetes what you want, and it figures out what to do.

Create Fails on Existing Resources

# First run — succeeds
kubectl create -f deployment.yaml
# deployment.apps/web created

# Second run — fails
kubectl create -f deployment.yaml
# Error from server (AlreadyExists): deployments.apps "web" already exists
# First run — creates
kubectl apply -f deployment.yaml
# deployment.apps/web created

# Second run — no-op or updates
kubectl apply -f deployment.yaml
# deployment.apps/web unchanged (or configured if changed)

This idempotency makes apply safe for CI/CD pipelines and GitOps — you can run it on every commit without worrying whether the resource already exists.

The Three-Way Merge

When you run kubectl apply, Kubernetes performs a three-way merge between:

  1. Last-applied configuration — stored as an annotation on the resource
  2. Current live state — what is actually running in the cluster
  3. New configuration — the YAML file you are applying
# The annotation that apply stores
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment",...}

This three-way merge allows apply to correctly handle fields that were:

  • Added in the new config (they get created)
  • Removed from the new config (they get deleted)
  • Changed by another tool or user (conflicts are detected)

create does not store this annotation, so if you create a resource with create and later try to apply changes, the merge behavior may be unexpected.

Server-Side Apply

Kubernetes 1.22+ supports server-side apply, which improves on client-side apply:

kubectl apply -f deployment.yaml --server-side

Server-side apply tracks field ownership — it knows which manager (user, CI pipeline, controller) set each field. This prevents conflicts when multiple tools manage the same resource:

# CI pipeline owns the image and replicas
kubectl apply -f deployment.yaml --server-side --field-manager=ci-pipeline

# HPA owns the replicas field
# No conflict because HPA's field manager is different

Common kubectl create Patterns

While apply is preferred for production, create has useful patterns:

# Generate YAML without creating the resource
kubectl create deployment web --image=nginx:latest \
  --replicas=3 --dry-run=client -o yaml > deployment.yaml

# Create a namespace
kubectl create namespace staging

# Create a secret imperatively (avoids base64 encoding)
kubectl create secret generic db-creds \
  --from-literal=password=mysecret \
  --from-file=tls.crt=./cert.pem

# Create a ConfigMap from a file
kubectl create configmap app-config \
  --from-file=config.yaml=./config.yaml

# Create a Job (one-off, should not already exist)
kubectl create job db-migrate --image=my-app:1.0.0 \
  -- python manage.py migrate

The --dry-run=client -o yaml pattern is especially valuable — it uses Kubernetes as a YAML generator, then you commit and apply the output.

Other Imperative Commands

Beyond create, kubectl has other imperative commands:

# Scale a deployment
kubectl scale deployment web --replicas=5

# Update an image
kubectl set image deployment/web nginx=nginx:1.26

# Expose a deployment as a service
kubectl expose deployment web --port=80 --target-port=8080

# Delete a resource
kubectl delete -f deployment.yaml

These are convenient for ad-hoc operations, but in a production GitOps workflow, you would update the YAML file and apply it instead, keeping the Git repository as the source of truth.

apply with Directories and URLs

apply works with directories, URLs, and Kustomize:

# Apply all YAML files in a directory
kubectl apply -f ./manifests/

# Apply from a URL
kubectl apply -f https://raw.githubusercontent.com/org/repo/main/deploy.yaml

# Apply with Kustomize
kubectl apply -k ./overlays/production/

# Apply with recursive directory traversal
kubectl apply -f ./manifests/ -R

create does not support the -k (Kustomize) flag.

Best Practices

  1. Use apply for all production resources — it is idempotent, supports updates, and works with GitOps
  2. Use create --dry-run=client -o yaml to generate templates — then edit and apply them
  3. Use create for one-off resources like Jobs, ad-hoc Secrets, or namespace initialization
  4. Do not mix create and apply for the same resource — the missing last-applied-configuration annotation causes merge issues
  5. Adopt server-side apply for multi-team environments where field ownership matters

The Declarative Workflow

# 1. Generate initial YAML
kubectl create deployment web --image=nginx:latest \
  --dry-run=client -o yaml > deployment.yaml

# 2. Edit the YAML (add resources, probes, etc.)
vim deployment.yaml

# 3. Apply to create
kubectl apply -f deployment.yaml

# 4. Make changes in the YAML file
vim deployment.yaml

# 5. Apply to update
kubectl apply -f deployment.yaml

# 6. Commit to Git
git add deployment.yaml && git commit -m "Update web deployment"

This workflow keeps the YAML file as the source of truth, makes changes reviewable in pull requests, and supports automated deployment pipelines.

Use kubectl apply when...

  • You're managing resources declaratively from version-controlled YAML files
  • Your CI/CD pipeline deploys by applying manifests
  • You want idempotent operations that can be re-run safely
  • You need to update existing resources with changed configuration
  • You're using GitOps with Argo CD or Flux

Use kubectl create when...

  • You're quickly creating a resource during development
  • You want to generate YAML with --dry-run=client -o yaml
  • You're creating a resource that should not already exist (like a Job)
  • You want explicit failure if the resource already exists

Model Interview Answer

kubectl apply is the declarative approach — you provide the desired state in a YAML file and Kubernetes figures out whether to create or update the resource. It stores the last-applied configuration as an annotation and uses a three-way merge (last-applied, live state, new config) to determine what to change. This makes it idempotent and safe to run repeatedly. kubectl create is imperative — it creates a resource and fails if it already exists. In production, apply is the standard because it supports iterative updates, works with GitOps workflows, and is safe in CI/CD pipelines. create is useful for one-off tasks and for generating YAML templates with --dry-run=client.

Related Comparisons