Imperative kubectl: run, create, expose, scale
The set of commands that create resources without YAML, and when each one fits.
A set of commands that create resources without YAML, and when each one fits. You'll need everything from the lesson on the first Pod and on dry-run.
Imperative and declarative
Kubernetes has two styles of working with resources.
Imperative: you tell kubectl what to do right now.
kubectl create deployment app --image=nginx:1.27.3-alpine is the command
"create a Deployment with these parameters". If that Deployment already
exists, the command fails:
error: failed to create deployment: deployments.apps "app" already exists
kubectl builds the manifest itself and sends it to the API server.
Declarative: you write the desired state in YAML and call kubectl apply -f. If the resource exists, kubectl compares it with the manifest and
updates it. If not, it creates it.
| Command | Resource doesn't exist | Resource already exists |
|---|---|---|
kubectl create | creates | fails with already exists |
kubectl apply -f | creates | compares and updates |
Production works declaratively: YAML lives in git, and every change is visible through a merge request. For a quick hypothesis check or one-off debugging, imperative is faster.
Map: what to use when
There are a lot of commands below. To keep track, remember which command fits which task:
- Quick Pod for debugging:
kubectl run. - A lasting service:
kubectl create deployment+kubectl expose. - Change something on the fly:
kubectl scale,kubectl set. - Need full control over YAML (probes, resources, securityContext): generate
a skeleton with
--dry-run=client -o yaml, then edit it by hand andapply.
Next, each command on its own.
kubectl run
Creates a single Pod. The most compact command in kubectl.
kubectl run temp --image=busybox:1.36.1 --restart=Never -- sh -c 'sleep 3600'
How to parse it: Pod name, then flags, then after -- the command for the
container.
k run temp --image=busybox:1.36.1 -- sleep 60
│ │ │ └ command
│ │ └ flags
│ └ Pod name
└ subcommand
--restart sets restartPolicy. Values: Always (the default),
OnFailure, Never. Older kubectl created a Deployment with
--restart=Always and a single Pod with Never. That logic was removed in
kubectl 1.18. Now run always creates a Pod, and the --restart value just
goes into its spec.
Useful run flags:
--imageis required.--portadds acontainerPort.--env KEY=valan environment variable.--labels key=val,key2=val2labels on the Pod. Without this flag kubectl sets the labelrun: <Pod name>.--rm -itrun interactively and delete the Pod on exit. Handy for debugging.--dry-run=client -o yamldon't create it, print the manifest instead.
kubectl create
Creates resources above the Pod level: Deployment, Service, ConfigMap, Secret, Job, CronJob, Namespace, ServiceAccount.
kubectl create deployment myapp --image=nginx:1.27.3-alpine \
--replicas=3 --port=80
kubectl create configmap app-cfg --from-literal=MODE=prod
kubectl create secret generic db --from-literal=PASS=lab-only-password
kubectl create job hello --image=busybox:1.36.1 -- echo done
kubectl create cronjob clean --image=busybox:1.36.1 \
--schedule='*/5 * * * *' -- sh -c 'echo tick'
Unlike kubectl run, you name the resource type yourself: deployment,
configmap, job. Short names work too: deploy, cm.
kubectl create deployment fills in two things on its own, which other
commands later ask about:
- the label
app: myappon the Pod template and the same selector on the Deployment; - the container name. It comes from the image name without the tag: for
nginx:1.27.3-alpinethe container is namednginx.
kubectl expose
Creates a Service on top of an existing Pod, Deployment, ReplicaSet, or another Service. The most common case: on top of a Deployment.
kubectl expose deployment myapp --port=8080 --target-port=80 --name=myapp-svc
--port is the Service's own port, the one clients hit. --target-port is
the container port the Service forwards the request to. If --target-port
isn't set, it equals --port.
expose takes the selector from the Deployment (its spec.selector), or
from the Pod's labels for a Pod. The default type is ClusterIP, change it
with --type=NodePort or --type=LoadBalancer. The Service name matches
the resource's name by default, the --name flag sets a different one.
Check the result by the addresses the Service picked up:
kubectl get endpointslice -l kubernetes.io/service-name=myapp-svc
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
myapp-svc-m4w9v IPv4 80 10.244.0.8,10.244.0.9,10.244.0.7 7s
The older command kubectl get endpoints myapp-svc shows the same thing,
but the v1 Endpoints object was deprecated as of Kubernetes 1.33, and
kubectl prints a warning:
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
kubectl scale
Changes spec.replicas on a Deployment, ReplicaSet, or StatefulSet.
kubectl scale deployment myapp --replicas=5
Under the hood it's a single PATCH request to the Deployment's scale
subresource, the command doesn't touch the object's other fields. You can
run it as many times as you want.
--dry-run=client -o yaml
You can turn any imperative command into a YAML generator by adding two flags:
kubectl create deployment myapp --image=nginx:1.27.3-alpine \
--replicas=3 --port=80 --dry-run=client -o yaml > deploy.yaml
--dry-run=client means "don't send it to the API, just build the manifest
locally." -o yaml means "print it as YAML." The result is a ready skeleton
you can edit by hand: add probes, resources, volumes, securityContext.
dry-run is covered in detail in the previous lesson.
The basic habit: you don't write YAML from scratch, you generate it and edit it by hand.
# Pod
kubectl run pod1 --image=nginx:1.27.3-alpine --dry-run=client -o yaml
# Deployment
kubectl create deployment d1 --image=nginx:1.27.3-alpine --dry-run=client -o yaml
# ConfigMap
kubectl create cm c1 --from-literal=k=v --dry-run=client -o yaml
# Job
kubectl create job j1 --image=busybox:1.36.1 --dry-run=client -o yaml -- sh -c 'echo hi'
# ClusterIP Service on top of an already created Deployment myapp
kubectl expose deployment myapp --port=80 --dry-run=client -o yaml
expose has a quirk. It reads the selector from the live object, so the
Deployment must already exist in the cluster. For a Deployment that doesn't
exist, even with --dry-run=client you get an error:
Error from server (NotFound): deployments.apps "d1" not found
kubectl set
Changes specific fields on an already created resource. Faster than edit.
# Change the nginx container's image
kubectl set image deployment/myapp nginx=nginx:1.27.4-alpine
# Change an env var
kubectl set env deployment/myapp DEBUG=true
# Change resources
kubectl set resources deployment/myapp --limits=cpu=200m,memory=256Mi
In set image, the name to the left of = is the container name, not the
Deployment's. Get it wrong and kubectl answers:
error: unable to find container named "app"
The container name is visible in kubectl describe deployment myapp, the
line under Containers:.
Each of these commands changes the Pod template, so the Deployment starts a
rollout: it creates new Pods and removes the old ones. The rollout's
progress shows in kubectl rollout status deployment/myapp.
kubectl edit
Opens the resource's current YAML in the editor from the KUBE_EDITOR or
EDITOR variable. If neither is set, kubectl uses vi, and notepad on
Windows. Save it, and kubectl sends the change. Handy for one-off edits, but
production doesn't work this way: the change isn't in git, and the next
apply from the repository will overwrite it.
kubectl edit deployment myapp
Common mistakes
Running kubectl create again on an existing resource. It doesn't update
the object, it fails with already exists. Delete the object and recreate
it, or fix it with set, scale.
Mixed-up ports in kubectl expose. The client hits --port, the app
listens on --target-port. If --target-port doesn't match the port the
container listens on, a request through the Service gets
Connection refused. kubectl describe service helps you compare ports:
the Port, TargetPort, and Endpoints lines.
A container command without -- in kubectl run. Everything after --
goes to the container. Without a long-running command, a Pod on busybox
finishes right away and goes to Completed.
An image without a tag in --image. Then you get whatever's currently under
the default tag in the registry, and tomorrow that might be a different
version. Write the tag explicitly, and after set image check
kubectl get pods: a typo in the tag shows up as ErrImagePull or
ImagePullBackOff.
What you'll need for the tasks
- Docker, kind v0.31.0 or newer, kubectl v1.35 or v1.36. Each lab's cluster
is brought up by its
setup/up.sh. kubectl create deployment,kubectl expose,kubectl run,kubectl scale,kubectl set image, and their--help.kubectl getandkubectl describefor Deployment, Service, and Pod.kubectl exec <pod> -- wget -qO- -T 5 http://<service>:<port>, to check a Service from inside the cluster.kubectl delete, to remove an object created with a mistake.
Homework
Practice the theory on your own cluster: 4 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.
./run.sh start k8s-imperative-commands # set up the starting state
./run.sh verify k8s-imperative-commands # check the current step
In short
createfails withalready existson an existing resource,applyupdates it.kubectl runcreates a single Pod,kubectl createresources above the Pod level.kubectl exposeputs a Service on top of a Deployment and takes the selector from it on its own.--portis the Service port,--target-portis the container port.kubectl scale deployment X --replicas=Nchanges the replica count with one PATCH.- Any imperative command with
--dry-run=client -o yamlturns into a manifest generator. kubectl set image/env/resourcesfixes one field without an editor.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-basics-07-imperative-commands
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress