kubectl apply vs set: Choosing the Right Command for Resource Updates

Understand when to use kubectl apply, set, and edit without creating drift between live Kubernetes objects and Git.

kubectl apply vs set: Choosing the Right Command for Resource Updates

The difference between kubectl apply and kubectl set is not just syntax. It is the difference between managing Kubernetes from a declared source of truth and changing live objects directly. Both are useful. Both can hurt you if you use them in the wrong place.

Use kubectl apply when the change should become the desired state of the system. Use kubectl set when you need a focused live change, usually temporary or urgent. Use kubectl edit when you need to inspect and patch a live object interactively, but understand that it is the easiest way to create drift from Git.

A Kubernetes object has a desired state stored in the API server. A Deployment says how many replicas should exist, which image should run, which labels identify pods, which resources are requested, and more. Controllers work to make reality match that desired state. Your update command changes the desired state; the controllers do the rest.

With kubectl apply, you keep that desired state in YAML or JSON files. The file is the thing you review, commit, promote, and roll back. A typical command is simple:

kubectl apply -f deployment.yaml

If the object does not exist, Kubernetes creates it. If it exists, Kubernetes updates it to match the manifest. Applying the same file again should not cause a new behavioral change. That idempotence is one reason apply works well in CI/CD and GitOps workflows.

Client-side kubectl apply historically used a last-applied annotation to calculate changes. Server-side apply, enabled with --server-side, tracks field ownership through managed fields in the API server. The details differ, but the operational idea is the same: a declared configuration owns the desired state.

Here is a small Deployment manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80

If you change the image in the file and run kubectl apply -f deployment.yaml, the Deployment updates. If the file is in Git, the change can be reviewed. If the rollout breaks, you can revert the commit or use Kubernetes rollout history depending on how your deployment process records revisions.

kubectl set works differently. It changes a specific field on a live object. The common example is changing a container image:

kubectl set image deployment/web nginx=nginx:1.26

That command is fast and readable. During an incident, speed matters. If the current image is broken and you need to move a Deployment to a known-good image, kubectl set image may be the quickest path. The danger is what happens next. If deployment.yaml still says nginx:1.25, the next kubectl apply -f deployment.yaml may move the workload back to 1.25.

That mismatch is configuration drift. The live cluster says one thing. The source file says another. Drift is not always catastrophic, but it makes debugging harder because people stop trusting the repo. Someone asks, "What is running in production?" and the honest answer becomes, "Let me check the cluster."

kubectl set has several useful subcommands:

kubectl set image deployment/web nginx=nginx:1.26
kubectl set env deployment/web FEATURE_FLAG=true
kubectl set resources deployment/web -c=nginx --requests=cpu=200m,memory=256Mi --limits=cpu=500m,memory=512Mi

These are practical for development clusters, demos, short-lived tests, and emergency production changes with a follow-up commit. They are not a replacement for maintained manifests.

kubectl edit fetches the live object, opens it in your editor, and sends the changed object back to the API server when you save:

kubectl edit deployment/web

It is convenient because you can see the full live YAML, including fields added by controllers. It is also risky because live objects contain many fields you should not manually manage, such as status, generated metadata, resource versions, and managed fields. Kubernetes will ignore or reject some invalid edits, but not every bad edit is syntactically invalid.

A common safe use of kubectl edit is a quick non-production experiment: increase replicas, change an annotation, or test a probe value. A common unsafe use is hand-editing production Deployments every week and never updating the manifests. That creates a cluster no one can rebuild confidently.

The practical rule is this: if you want the change to survive the next deployment, put it in the manifest and use apply. If you only need to poke the live object, use set or edit, then either revert the change or backport it to Git.

There are a few cases where imperative commands are not only acceptable but helpful. During debugging, you might temporarily add an environment variable that increases log verbosity:

kubectl set env deployment/api LOG_LEVEL=debug

After collecting logs, remove it:

kubectl set env deployment/api LOG_LEVEL-

If the debug setting should become permanent, commit it to the manifest instead. Do not rely on memory.

Another case is emergency rollback. If your deployment pipeline is stuck and customers are affected, setting the image directly may be reasonable:

kubectl set image deployment/api api=registry.example.com/api:2026-05-23-good
kubectl rollout status deployment/api

The follow-up should happen immediately: open a pull request or commit that makes the declared manifest match the emergency state, or run the normal rollback process once the pipeline is healthy. The live fix buys time; it should not become the new undocumented deployment method.

kubectl apply also has traps. If you mix multiple tools that manage the same fields, you can get conflicts or surprising overwrites. For example, a GitOps controller, Helm, and a human running kubectl apply against the same Deployment can all believe they own part of the object. Pick clear ownership. If Helm manages the resource, update the Helm values and run the Helm release process. If Argo CD or Flux manages it, change Git and let the controller reconcile.

For secrets and config, be especially careful. kubectl set env can put quick changes into a Deployment, but it may expose values in shell history or audit logs. For sensitive values, update the Secret through your normal secret management process. Do not paste production credentials into an ad hoc command unless your team has explicitly accepted that workflow.

Before changing a live object, inspect it:

kubectl get deployment web -o yaml
kubectl diff -f deployment.yaml

kubectl diff is underused. It shows what apply would change before you make the change. In production, that preview can catch mistakes like accidentally removing a label selector, dropping a resource limit, or applying the wrong environment's manifest.

For server-side apply, the command looks like this:

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

Server-side apply can be useful when multiple actors manage different fields, but it does not remove the need for ownership discipline. If two managers try to own the same field, Kubernetes may report a conflict. That is a feature; it is telling you the workflow is ambiguous.

Here is a simple decision guide I use in real clusters. New application release? Change the manifest and use apply through the pipeline. Increase replicas for a load test in staging? kubectl scale or kubectl set is fine if you revert it. Hotfix a broken image in production? kubectl set image can be acceptable, but create the source-of-truth change right away. Tune CPU requests permanently? Update the manifest. Explore what fields exist on a resource? Use kubectl get -o yaml before reaching for edit.

When teams get this right, Kubernetes becomes easier to reason about. The repo tells the story. The cluster matches the repo most of the time. Temporary live changes are labeled as temporary and cleaned up. Incidents are still stressful, but the configuration does not become a second mystery.

The command itself is not the point. The point is whether you can rebuild the cluster state tomorrow from trusted files. kubectl apply supports that habit. kubectl set and kubectl edit are sharp tools for moments when direct action is useful. Keep that boundary clear and you will avoid a lot of avoidable Kubernetes confusion.

There is another command people put in the same mental bucket: kubectl patch. It is also imperative, but it is better for precise scripted changes than edit. For example, you can patch a Deployment annotation to trigger a restart or update a small field in automation. The same drift rule applies. If the patched field represents long-term desired state, update the source manifest too.

kubectl patch deployment web   -p '{"spec":{"template":{"metadata":{"annotations":{"restartedAt":"2026-05-24T10:00:00Z"}}}}}'

For restarts, prefer the purpose-built command:

kubectl rollout restart deployment/web

That command still changes the live object, but its intent is clear: start a new rollout from the current pod template. It is not a substitute for changing configuration in Git.

In GitOps environments, the boundary is even stricter. If Argo CD or Flux owns an object, a manual kubectl set image may be reverted automatically because the controller sees drift and reconciles back to Git. That can be surprising during an incident. Before making a manual production change, know whether a GitOps controller will fight you. Sometimes the right emergency action is to pause reconciliation for that application, make the fix, then commit the matching Git change and resume reconciliation.

You should also know how to capture a live difference without blindly committing generated fields. kubectl get deployment web -o yaml includes status, resource versions, managed fields, and other data that should not go into a clean manifest. If you need to backport a hotfix, edit the source manifest by hand or use a tool such as Kustomize or Helm values, then run kubectl diff to verify the intended change. Do not replace your source file with raw live YAML unless you clean it carefully.

For teams, the healthiest policy is usually short and explicit. Production changes go through Git. Emergency live changes are allowed when needed, but they require a follow-up source change or rollback. Development clusters are looser, but anything promoted beyond development must be declared. That policy is easier to follow than a long list of forbidden commands.

There is a human side to this too. During an outage, the person with cluster access may make the fastest possible fix. That is fine when the team treats it as an emergency exception. It becomes dangerous when those exceptions become normal operations. If production is routinely fixed by hand, the deployment process is either too slow, too fragile, or not trusted. Fix that process rather than teaching everyone more live-edit tricks.

RBAC can reinforce the workflow. Many teams allow broad kubectl access in development but restrict production writes to CI/CD systems, GitOps controllers, or a small on-call group. That is not bureaucracy for its own sake. It reduces the number of paths that can change desired state. When something changes, audit logs and Git history are easier to follow.

For learning, it is still worth practicing all three commands in a disposable namespace. Create a Deployment with apply, change its image with set, inspect the drift with kubectl diff, then update the manifest and apply again. Seeing the drift happen once in a safe environment makes the production rule much easier to remember.