kubectl apply and delete

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

Manage Kubernetes objects declaratively. Apply vs create vs replace, server-side apply, kubectl diff, cascading deletion, and force-deleting a stuck resource.

apply makes the cluster match a file. delete removes objects. Between them they cover most of how real workloads are managed.

The important habit is that manifests live in version control and apply is how they reach the cluster, so the repository, not the cluster, is the source of truth.

apply

kubectl apply -f deployment.yaml
kubectl apply -f ./manifests/                # every file in a directory
kubectl apply -f ./manifests/ -R             # and subdirectories
kubectl apply -k ./overlays/production        # a Kustomize overlay

It creates the object if absent and updates it if present, which makes it safe to run repeatedly. The same command works on first deploy and every deploy after.

Several objects in one file, separated by ---, are applied in order:

apiVersion: v1
kind: Service
metadata:
  name: payments-api
# ...
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payments-api
# ...

apply vs create vs replace

CommandBehaviourWhen
createFails if the object existsOne-off creation, or generating YAML
applyCreates or updates, merging changesNearly always
replaceOverwrites the whole object; fails if absentRare (discards fields set by controllers)
patchChanges named fields onlyScripted, targeted edits

The mechanism behind apply is a three-way merge between the last applied configuration, your file, and the live object. That is why it can change one field without discarding values written by controllers, and why the annotation kubectl.kubernetes.io/last-applied-configuration exists on objects you have applied.

It also explains a common surprise: removing a field from your manifest removes it from the cluster, because the merge can see it was there before and is not now. A field you never declared at all is left alone.

Mixing apply with kubectl edit or console changes muddles that history and produces confusing results. Pick one.

Preview before applying

kubectl diff -f deployment.yaml
--- live
+++ merged
   spec:
-    replicas: 3
+    replicas: 5
     template:
       spec:
         containers:
         - name: api
-          image: ghcr.io/acme/payments-api:1.4.2
+          image: ghcr.io/acme/payments-api:1.5.0

This is the closest equivalent to terraform plan, and it is worth running before any change to a production namespace.

kubectl apply -f deployment.yaml --dry-run=server

--dry-run=server sends the request to the API server, which runs validation and admission controllers without persisting anything. It catches schema errors, policy rejections and missing CRDs that client-side validation cannot see. --dry-run=client only formats locally. Useful for generating YAML, not for validating it.

Server-side apply

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

The newer mechanism. Instead of relying on a last-applied annotation, the API server records field ownership for every manager that writes to the object. If two controllers try to own the same field, you get an explicit conflict rather than a silent overwrite:

Apply failed with 1 conflict: conflict with "hpa-controller": .spec.replicas

That message is genuinely useful. Here it is telling you a HorizontalPodAutoscaler owns the replica count and your manifest should not declare it.

--force-conflicts takes ownership anyway, which is sometimes correct and should be a decision rather than a reflex.

delete

kubectl delete -f deployment.yaml        # the safest form
kubectl delete deployment payments-api
kubectl delete pods -l app=payments-api
kubectl delete pods --all -n staging

Prefer delete -f with the same file you applied. Deleting by name is easy to get wrong, particularly when the name is similar across namespaces.

Deletion cascades

Deleting a Deployment deletes its ReplicaSets and their Pods, through owner references.

# Delete the Deployment but keep the Pods running
kubectl delete deployment payments-api --cascade=orphan

--cascade=orphan is occasionally useful, migrating Pods to a new controller, for instance, and leaves Pods that nothing manages, so use it deliberately.

Deleting a Pod managed by a Deployment does not remove it: the controller notices the shortfall and creates a replacement. That is how you restart a single Pod.

Deleting a namespace

kubectl delete namespace staging

This removes everything in it. Deployments, Services, Secrets, ConfigMaps and PersistentVolumeClaims. With a StorageClass whose reclaim policy is Delete, the underlying disks and their data go too. It is among the most destructive single commands available.

When something will not delete

An object stuck in Terminating is almost always waiting on a finalizer. A marker saying "some controller must clean up before this can go".

kubectl get pod <name> -o jsonpath='{.metadata.finalizers}'

For Pods, the usual cause is a node that has gone away, so the kubelet cannot confirm the containers stopped:

kubectl delete pod <name> --grace-period=0 --force

This tells the API server to forget the Pod without confirmation. On a node that is merely unreachable rather than dead, the containers may still be running, which for a StatefulSet member holding a volume can mean two instances writing to the same data. Force-delete after establishing the node is genuinely gone, not before.

For other objects, removing a finalizer by hand is a last resort:

kubectl patch pvc <name> -p '{"metadata":{"finalizers":null}}' --type=merge

The correct fix is nearly always to remove whatever is blocking cleanup. A Pod still using a PVC, or an operator that was uninstalled before its custom resources were deleted.

Pruning

kubectl apply -f ./manifests/ --prune -l app=payments-api

--prune deletes objects matching the selector that are no longer in the manifests, which is how you make apply genuinely declarative. It is powerful and easy to misuse. A wrong label selector deletes more than you intended. In practice, most teams get this behaviour from ArgoCD or Flux instead, where it is scoped and previewed properly.

Quick reference

TaskCommand
Apply a filekubectl apply -f file.yaml
Apply a directorykubectl apply -f ./manifests/ -R
Previewkubectl diff -f file.yaml
Validate fullykubectl apply -f file.yaml --dry-run=server
Field-ownership applykubectl apply -f file.yaml --server-side
Delete what you appliedkubectl delete -f file.yaml
Delete by labelkubectl delete pods -l app=<label>
Keep the childrenkubectl delete deployment <n> --cascade=orphan
Force a stuck Podkubectl delete pod <n> --grace-period=0 --force
Inspect finalizerskubectl get <res> <n> -o jsonpath='{.metadata.finalizers}'

Key takeaways

  • apply creates or updates and is safe to repeat. create fails on existing objects; replace overwrites wholesale.
  • Removing a field from your manifest removes it from the cluster: that is the three-way merge working.
  • kubectl diff -f before applying to anything that matters.
  • --dry-run=server validates against admission controllers; --dry-run=client only formats.
  • Server-side apply reports ownership conflicts explicitly instead of silently overwriting.
  • Deleting a namespace deletes its PVCs, and a Delete reclaim policy destroys the data.
  • Force-deleting a Pod on an unreachable node can leave containers running. Confirm the node is gone first.

Check your understanding

Exercise

A setting that disappeared

A Deployment manifest previously set nodeSelector. Someone removes those lines and runs kubectl apply. The live Deployment no longer has a nodeSelector. Is that expected?

Exercise

Force-deleting on a partitioned node

A StatefulSet Pod is stuck Terminating because its node is unreachable. What is the risk of kubectl delete pod --grace-period=0 --force?


Frequently Asked Questions

What is the difference between kubectl apply and kubectl create?

create maps to an HTTP POST and fails if the object already exists, so it only handles first creation. apply creates the object when absent and updates it when present, using a three-way merge between the last applied configuration, your file, and the live object. That merge lets it update your fields without discarding values set by controllers, and makes running the same command repeatedly safe. Use apply for managed manifests and create mainly with --dry-run=client -o yaml to generate them.

What does kubectl diff do?

kubectl diff -f manifest.yaml computes what applying that file would change and prints a unified diff against the live objects, without modifying anything. It is the nearest equivalent to terraform plan and worth running before any change to a production namespace. Pair it with kubectl apply --dry-run=server, which sends the request to the API server for full validation including admission controllers but does not persist it.

What is server-side apply and should I use it?

With server-side apply the API server records which manager owns each field, so two controllers writing the same field produce an explicit conflict instead of silently overwriting each other. A message like conflict with "hpa-controller": .spec.replicas is telling you that an autoscaler owns the replica count and your manifest should not declare it. It is the more robust mechanism and is worth adopting, particularly where several controllers touch the same objects. --force-conflicts overrides ownership and should be a considered decision.

Does deleting a Deployment delete its Pods?

Yes. Owner references make deletion cascade from Deployment to ReplicaSets to Pods. --cascade=orphan keeps the children, which is occasionally useful when migrating workloads between controllers but leaves Pods that nothing manages. Note the reverse is not true: deleting a Pod that belongs to a Deployment simply causes the controller to create a replacement, which is exactly how you restart a single Pod.

Why is my Pod stuck in Terminating?

Almost always a finalizer, and for Pods the usual cause is an unreachable node. The API server will not remove the Pod until the kubelet confirms the containers stopped, and a node that is down never will. kubectl delete pod <name> --grace-period=0 --force removes it from the API. Be careful with StatefulSet Pods: if the node is merely partitioned rather than dead, the container is still running with its volume attached, and a replacement Pod writing the same data can corrupt it.

What does --prune do and is it safe?

kubectl apply -f ./manifests/ --prune -l app=myapp deletes objects matching the label selector that are no longer present in the applied manifests, which is what makes apply fully declarative rather than additive. It is genuinely useful and genuinely dangerous, because a wrong or overly broad selector deletes more than intended. Most teams get the same behaviour from ArgoCD or Flux, where pruning is scoped to an application and previewed before it runs.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources