Skip to content
Commitpedia
Log in Join & edit

Deploy your first app on Kubernetes

A step-by-step tutorial: write a Deployment and a Service in YAML, apply them, reach the app, add health probes, roll out a new version and roll it back.

Not checked by a trusted editor yet Last edited Outdated?

Edit history of Deploy your first app on Kubernetes: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
4 min

This tutorial deploys a small web server (nginx) to a Kubernetes cluster, makes it reachable, and then walks through an update and a rollback. It works on any conformant cluster: a local one such as kind, minikube or k3s, or a test namespace on a shared cluster.

Before you start

  • kubectl installed and pointing at a cluster where you may create a namespace. Check with kubectl config current-context, so you are not about to deploy into production by mistake.
  • kubectl get nodes shows at least one node in the Ready state.

Step 1: write the manifest

Save the following as hello.yaml. It contains three objects separated by ---: a namespace, a Deployment with two replicas and a Service in front of them.

apiVersion: v1
kind: Namespace
metadata:
  name: hello
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
  namespace: hello
  labels:
    app: hello
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: web
        image: nginx:1.27
        ports:
        - name: http
          containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 64Mi
          limits:
            memory: 128Mi
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 10
          failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: hello
  namespace: hello
spec:
  selector:
    app: hello
  ports:
  - name: http
    port: 80
    targetPort: http

What the important fields do:

  • selector.matchLabels and template.metadata.labels must match. The Service uses the same app: hello label to find the Pods.
  • resources.requests is what the scheduler reserves on a node (100m means a tenth of a CPU core). limits.memory is a hard ceiling; a container that goes over it can be OOM-killed.
  • targetPort: http refers to the named container port, so you can change the number in one place.

Step 2: apply it

kubectl apply -f hello.yaml
kubectl get all -n hello
kubectl rollout status deployment/hello -n hello

kubectl apply is declarative: run it again after editing the file and Kubernetes changes only what differs. Wait until rollout status reports that the deployment was successfully rolled out and both Pods show READY 1/1.

Step 3: reach the app

The Service is of type ClusterIP, so it is only reachable inside the cluster. For testing, forward a local port to it:

kubectl port-forward -n hello service/hello 8080:80
curl http://localhost:8080/

You should see the nginx welcome page. Press Ctrl+C to stop the forward. To expose the app permanently, change the Service type to NodePort or LoadBalancer, or add an Ingress.

Step 4: understand the probes

The kubelet runs the probes you define and acts on the result:

  • Readiness probe: while it fails, the Pod is removed from the Service’s endpoints and gets no traffic, but it is not restarted. Use it for “I am up but not ready yet” (warming caches, waiting for a dependency).
  • Liveness probe: after failureThreshold consecutive failures, the kubelet restarts the container. Use it only to detect a stuck process, and keep it cheap: it should not check databases or other services.
  • Startup probe: for slow-starting applications. Liveness and readiness checks are held back until it succeeds once. Give it a failureThreshold * periodSeconds long enough to cover the worst-case start time; for example 30 x 10 seconds allows five minutes.
startupProbe:
  httpGet:
    path: /
    port: http
  failureThreshold: 30
  periodSeconds: 10

Probes can use httpGet, tcpSocket, grpc or exec (run a command in the container). A liveness probe that is too strict is a common cause of endless restarts, so start with generous values.

Step 5: roll out a new version

Change the image tag. You can edit hello.yaml and apply it again (preferred, because the file stays the source of truth), or use kubectl set image for a quick test:

kubectl set image deployment/hello -n hello web=nginx:1.28
kubectl annotate deployment/hello -n hello kubernetes.io/change-cause="nginx 1.28"
kubectl rollout status deployment/hello -n hello
kubectl get replicasets -n hello

The Deployment creates a new ReplicaSet and replaces Pods a few at a time. Thanks to the readiness probe, new Pods only receive traffic once they answer. The old ReplicaSet stays with zero replicas as history.

Step 6: roll back

Simulate a bad release by setting an image tag that does not exist, then undo it:

kubectl set image deployment/hello -n hello web=nginx:does-not-exist
kubectl get pods -n hello
kubectl rollout history deployment/hello -n hello
kubectl rollout undo deployment/hello -n hello
kubectl rollout status deployment/hello -n hello

The new Pod gets stuck in ErrImagePull or ImagePullBackOff, but the old Pods keep serving because the rolling update never removes more than the allowed number of ready Pods. rollout undo returns to the previous revision; add --to-revision=N to pick a specific one from the history.

Step 7: look around

kubectl describe deployment hello -n hello
kubectl logs -n hello deployment/hello
kubectl get events -n hello --sort-by=.metadata.creationTimestamp
kubectl scale deployment/hello -n hello --replicas=4

Step 8: clean up

Warning: deleting a namespace deletes every object inside it, including data in volumes that use a Delete reclaim policy. First check that it contains only this tutorial with kubectl get all -n hello, then run:

kubectl delete namespace hello

Official documentation

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Commitpedia +5,876 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?