skip to content

In Kubernetes, how do `kubectl create -f`, `kubectl replace -f` and `kubectl apply -f` differ when you manage an object from a YAML file?

level: juniorimportance: must knowfreq 74%

answer

  1. POST, PUT, PATCH
  2. which one fails on existing
  3. whole object versus declared fields
  4. fields someone else added
  5. --dry-run=server previews

basics

~20 s

kubectl create only makes new objects and fails if the name exists. kubectl replace overwrites an existing object with the whole file. kubectl apply creates or updates by merging the file into the live object and leaves fields it never set alone.

solid answer

~50 s

`kubectl create -f` is imperative: it POSTs a new object and fails with `AlreadyExists` if the name is taken. `kubectl replace -f` PUTs the file as the complete new object, so it fails with `NotFound` if nothing exists, and it drops any spec or metadata field another actor added that is not in your file. `kubectl replace --force` goes further: it deletes the object and recreates it. `kubectl apply -f` is declarative. It creates the object if it is missing, and otherwise it merges your file into the live object. It changes the fields you declare, removes fields you *used to* declare, and leaves other fields alone. That is why apply is the verb for manifests kept in Git and re-run on every deploy. To preview any of these, add `--dry-run=server` or run `kubectl diff -f`.

code

bash · 6 lines
bash
kubectl create -f records-portal.yaml
# Error from server (AlreadyExists): deployments.apps "records-portal" already exists

kubectl diff -f records-portal.yaml
kubectl apply -f records-portal.yaml --dry-run=server
kubectl apply -f records-portal.yaml

go deeper

for a junior

Know which verb fails on an existing object, which one fails on a missing one, and that apply is the re-runnable, declarative choice for manifests kept in Git.

for a middle

Explain the HTTP verbs underneath (POST, PUT, PATCH) and why apply only touches declared fields, while replace drops anything another actor added.

for a senior

Show that you preview with kubectl diff or --dry-run=server before changing production, and that you know replace --force deletes and recreates, taking the Pods with it.

for a principal

Frame the choice as an ownership model: whether one file owns the whole object, or several actors share it. Say which verbs your platform's pipelines are allowed to use.

## Three verbs, three models Kubernetes stores every object as a document in the API server. `kubectl` gives you three ways to push a YAML file into that store, and each one assumes a different model of who owns the object: - **Imperative create**: "make this new thing". - **Imperative replace**: "this file *is* the whole object now". - **Declarative apply**: "make the live object match what this file declares, and nothing more". The difference matters because a live object is rarely written by one hand. Controllers, autoscalers, on-call engineers and other tools add fields after you deploy. Take the clinical-records portal's Deployment, `records-portal`, on a 38-node cluster with a mixed spot and on-demand pool. Its file says `replicas: 7`, but during a 310 ms p99 latency spike an engineer ran `kubectl patch` to add a toleration so pods could land on spot nodes. ## kubectl create `kubectl create -f records-portal.yaml` sends a **POST**. If no Deployment with that name exists in the namespace, the object is created. If one exists, the command fails with `Error from server (AlreadyExists)` and changes nothing. It is right for one-off objects and scripts that must not touch an existing resource. It is wrong for a pipeline that runs on every commit, because the second run fails. By default `create` does not record the configuration it used. If you later switch to `kubectl apply`, kubectl prints a warning that the object is missing the annotation apply needs, and it patches that annotation in. `kubectl create --save-config` records it up front. ## kubectl replace `kubectl replace -f records-portal.yaml` sends a **PUT** with the file as the complete new object: 1. If the object does not exist, it fails with `NotFound`. 2. If it exists, the stored spec and metadata become whatever the file says. The server still fills defaults and keeps what it owns, such as `uid` and the status. 3. Anything another actor added to the spec that your file lacks is gone. In the portal example, the toleration the engineer patched in disappears. `kubectl replace --force` deletes the object and creates it again. For a Deployment that means its Pods go too, so you get a real outage, not a rolling update. ## kubectl apply `kubectl apply -f records-portal.yaml` is **declarative**. By default it runs client-side: - If the object is missing, apply creates it and records what you applied. - If it exists, apply builds a **patch**, not a full object. The patch sets the fields your file declares and deletes fields that were in your previous apply but are no longer in the file. - Fields you never declared, like the toleration added by `kubectl patch`, are left alone. - Fields you *do* declare are forced to your value. Applying `replicas: 7` after something scaled the Deployment to 13 sets it back to 7. With `--server-side`, the API server does the merge and tracks an owner for every field. That changes how disagreements are handled but keeps the same declarative contract. ## Side by side | | `create -f` | `replace -f` | `apply -f` | |---|---|---|---| | HTTP verb | POST | PUT | PATCH (or POST if missing) | | Object missing | creates | fails `NotFound` | creates | | Object exists | fails `AlreadyExists` | overwrites the whole spec | merges declared fields | | Fields others added | n/a | dropped | kept unless you declare them | | Safe to re-run | no | yes, but destructive | yes | ## Previewing a change All three accept `--dry-run`: - `--dry-run=client` only prints the object kubectl *would* send. Nothing reaches the cluster. - `--dry-run=server` sends the real request. The API server runs defaulting, validation and admission, then discards the result instead of storing it. - `kubectl diff -f records-portal.yaml` does a server-side dry run and shows the difference from the live object. It is the usual pre-merge check for a manifest change. ```bash kubectl diff -f records-portal.yaml kubectl apply -f records-portal.yaml --dry-run=server kubectl apply -f records-portal.yaml ``` ## Which to use Use `apply` for anything kept as a file and re-applied. Keep `create` for bootstrap scripts that must fail on a duplicate. Use `replace` only when you truly mean "this file is the entire truth", and use `replace --force` only when an immutable field forces a recreate and you have accepted the downtime.

  • You created an object with `kubectl create` and now run `kubectl apply` on it. What happens?
    The apply succeeds. kubectl warns that the object is missing the last-applied annotation that client-side apply needs, and it patches the annotation in. Because there was no earlier record, this first apply cannot tell which fields you *removed* from the file, so nothing is deleted. Deletion tracking starts from this apply onward. `kubectl create --save-config` avoids the gap.
  • When would `kubectl replace --force` be justified?
    When you must change a field the API server treats as immutable, such as a Deployment's `.spec.selector` or a Job's pod template. Those can only change by deleting and recreating the object. `--force` does exactly that, so the existing Pods are removed. Accept it only with a maintenance window or a second, parallel object that carries the traffic.

create is filing a new form, replace is swapping the whole form for your copy, and apply is editing only the boxes you filled in last time.

saying these in an interview costs you the question

  • kubectl create updates the object if it already exists
  • kubectl apply sends the whole file and overwrites everything
  • kubectl replace keeps fields that other tools added
  • --dry-run=client asks the API server to validate
  • replace --force performs a rolling update