Deploy Your First App on Kubernetes

Run a self-healing web app on a local Kubernetes cluster.

Introduction

30 Second Summary

Every time you order food, stream a show, or send a message, dozens of tiny applications work together behind the scenes. Kubernetes is the system that keeps all those applications running, automatically restarting anything that crashes and scaling up when demand spikes.

In this project, you will deploy your first application on a local Kubernetes cluster, expose it to your browser, inject custom configuration, and watch Kubernetes automatically heal itself when things break.

What You'll Build

A local Kubernetes environment running a web application managed by a Deployment, exposed via a Service, and configured with a ConfigMap.

By the end of this project, you'll have:

  • A running Minikube cluster on your Windows machine with control plane components you can inspect.
  • A Deployment managing multiple replicas of an nginx web server, exposed to your browser through a NodePort Service.
  • Evidence of self-healing in action: Kubernetes automatically replacing deleted Pods to maintain your desired state.
  • Secret Mission: Deploy a second application tier and connect it using Service DNS discovery.

Are there any prerequisites?

You need Docker Desktop installed and running on Windows with Linux containers enabled. Basic familiarity with typing commands in a terminal is helpful, but no prior Kubernetes experience is needed.

Install Minikube and kubectl

To run Kubernetes on your own machine, you need two tools.

The first is Minikube, which creates a local Kubernetes cluster inside a Docker container. The second is kubectl, the command-line tool that talks to your cluster. In this step, you will install both and confirm Docker Desktop is ready.

In this step, get ready to:
  • Install Minikube using Windows Package Manager.
  • Install kubectl using Windows Package Manager.
  • Confirm Docker Desktop is running with Linux containers.
Install Minikube using Windows Package Manager

Minikube is the tool that spins up a local Kubernetes cluster on your machine. You will install it using winget, the built-in Windows Package Manager.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Install Minikube by running this command:
winget install Kubernetes.minikube

What does this command do?

winget install Kubernetes.minikube downloads and installs the latest stable release of Minikube from the official package registry. It also adds the Minikube binary to your system PATH.

  • Wait for the installation to complete. You will see a success message in the terminal.
  • Close your PowerShell window completely.
  • Reopen PowerShell using the same method (press the Windows key, type PowerShell, press Enter).

Why close and reopen PowerShell?

When you install a new tool, Windows updates the system PATH. PowerShell only reads the PATH when it first opens. Closing and reopening gives it the fresh PATH that includes Minikube.

  • Verify Minikube is installed by running:
minikube version

✔️ I see a version number

Minikube is installed and ready. Move on to the next substep.

ⓧ Command not found

PowerShell cannot find the minikube command. Try these fixes:

  • Make sure you closed and reopened PowerShell after the install finished. A fresh window is required to pick up PATH changes.
  • Run winget install Kubernetes.minikube again to confirm the installation succeeded.
  • If it still fails, restart your computer to force a full PATH refresh, then open PowerShell and try minikube version again.
Install kubectl using Windows Package Manager

kubectl is the command-line tool you use to send instructions to your Kubernetes cluster. Every command you run against Kubernetes goes through kubectl.

  • Install kubectl by running this command:
winget install -e --id Kubernetes.kubectl

What does this command do?

winget install -e --id Kubernetes.kubectl installs the official Kubernetes command-line client. The -e flag ensures an exact match on the package ID, so you get the right tool.

  • Wait for the installation to complete.
  • Close your PowerShell window completely.
  • Reopen PowerShell (press the Windows key, type PowerShell, press Enter).
  • Verify kubectl is installed by running:
kubectl version --client

✔️ I see version info

kubectl is installed and ready. Move on to the next substep.

ⓧ Command not found

PowerShell cannot find the kubectl command. Try these fixes:

  • Make sure you closed and reopened PowerShell after the install finished.
  • Run winget install -e --id Kubernetes.kubectl again to confirm the installation succeeded.
  • If it still fails, restart your computer to force a full PATH refresh, then open PowerShell and try kubectl version --client again.
Verify Docker Desktop is running with Linux containers

Minikube uses Docker to run your cluster. It specifically needs Docker set to Linux container mode. Let's confirm your Docker Desktop is configured correctly.

  • Check Docker's container type by running this command:
docker info --format '{{.OSType}}'

This command asks Docker which operating system type it is using for containers.

✔️ I see linux

Docker Desktop is set to Linux containers. You are all set for the next step.

ⓧ I see windows

Docker Desktop is currently set to Windows containers. You need to switch to Linux containers:

  • Find the Docker icon in your system tray (bottom-right corner of your taskbar). You may need to click the up arrow to see hidden icons.
  • Right-click the Docker icon.
  • Click Switch to Linux containers.
  • Wait for Docker Desktop to restart.
  • Run docker info --format '{{.OSType}}' again. It should now return linux.

Docker command not responding?

  • Make sure Docker Desktop is running. Look for the Docker whale icon in your system tray.
  • If you just started Docker Desktop, give it a minute to finish initializing before running the command.

Help me troubleshoot Docker Desktop not responding to commands.

Your tools are installed and Docker is ready. Next up, you will start your Kubernetes cluster and explore its architecture.

Start Your Cluster and Explore the Architecture

Your tools are installed and ready to go. Now it's time to spin up your very own Kubernetes cluster.

A Kubernetes cluster has two main parts: a control plane that makes decisions about your applications, and worker nodes that actually run them. In this step, you will start a local cluster and explore its internals to see these components firsthand.

In this step, get ready to:
  • Start a local Kubernetes cluster using Minikube.
  • Explore the control plane components with kubectl.
  • View the default namespace and understand the starting state.
Start Minikube with the Docker driver

Minikube needs a driver to create your cluster's virtual environment. Since you already have Docker Desktop running, you will use the Docker driver. This avoids needing Hyper-V (which requires specific Windows editions and a reboot to enable).

  • Start your local Kubernetes cluster by running this command in your PowerShell window:
minikube start --driver=docker

What does this command do?

This creates a single-node Kubernetes cluster running inside a Docker container. Minikube bundles both the control plane and a worker node into one machine.

The control plane contains four key components: the API Server, etcd (a database that stores all cluster state), the Scheduler, and the Controller Manager. These are the exact components the KCNA exam tests you on.

The command takes a minute or two to complete. When it finishes, you will see a message that says kubectl is now configured to use "minikube" cluster and "default" namespace by default.

  • Verify the cluster is running:
kubectl cluster-info

You should see output showing that the Kubernetes control plane is running at a local address (something like https://127.0.0.1:PORT) along with a CoreDNS URL.

Cluster failed to start?

  • Make sure Docker Desktop is running. If you closed it after Step 1, reopen it and wait for the engine to start before trying again.
  • Confirm Docker is in Linux container mode by running docker info --format '{{.OSType}}'. If it returns windows, right-click the Docker icon in the system tray and select Switch to Linux containers.
  • If you see a timeout error, try running minikube delete and then minikube start --driver=docker again.

Still stuck? Help me troubleshoot why minikube start is failing on my Windows machine with Docker driver.

Explore cluster components with kubectl

Your cluster is running, but what's actually inside it? Let's poke around using kubectl to see the architecture in action.

  • List the nodes in your cluster by running:
kubectl get nodes

You should see one node named minikube with STATUS Ready. This single node plays both roles: control plane and worker.

  • View the system Pods that make up the control plane by running:
kubectl get pods -n kube-system

What are these Pods?

You will see Pods with names like etcd-minikube, kube-apiserver-minikube, kube-scheduler-minikube, and kube-controller-manager-minikube. These are the control plane components running as Pods.

Each one has a job. The API Server is the front door that all commands go through. etcd stores the cluster's entire state. The Scheduler decides which node runs each Pod. The Controller Manager watches for changes and takes corrective action.

Why does the KCNA care about this?

The Kubernetes Fundamentals domain makes up 44% of the KCNA exam. Questions in this domain test whether you understand what each control plane component does and how they work together. Seeing them live as Pods makes this easier to remember than reading a diagram.

Understand what you are looking at

So far you have been looking at system Pods in the kube-system namespace. Namespaces are a way to organize resources inside a cluster. Your applications will go in the default namespace.

  • Check what exists in the default namespace by running:
kubectl get all

You should see only a ClusterIP service for kubernetes itself. No other resources exist yet because you have not deployed anything.

Why is the default namespace empty?

Kubernetes separates its own infrastructure (in kube-system) from your workloads (in default). This keeps things organized and makes it harder to accidentally break the cluster by deleting a system Pod.

Your cluster is alive and you have met the components that power it. Next up, you will deploy your first application and see Kubernetes do what it does best: manage your workloads.

Deploy Your First Application

Your cluster is running and you have explored its control plane components. Now it is time to deploy an actual application.

A Deployment tells Kubernetes what container image to run and how many copies (replicas) you want. Kubernetes then works continuously to maintain that desired state. In this step, you will create a Deployment that runs 3 replicas of an nginx web server.

In this step, get ready to:
  • Create a Deployment with 3 replicas of nginx.
  • Inspect the Deployment, ReplicaSet, and Pod hierarchy.
  • View individual Pod details and scheduling events.
Create a Deployment using kubectl

Deployments are the most common way to run applications on Kubernetes and a major KCNA exam topic. You will use an imperative command to create one now.

  • Create a Deployment that runs 3 replicas of nginx by running this command:
kubectl create deployment my-web --image=nginx:1.27 --replicas=3

What does this command do?

  • kubectl create deployment my-web tells Kubernetes to create a Deployment named my-web.
  • --image=nginx:1.27 specifies the container image each replica will run.
  • --replicas=3 tells Kubernetes to maintain exactly 3 copies of the Pod at all times.
  • Watch the Pods come up by running this command:
kubectl get pods -w
  • Wait until all 3 Pods show STATUS Running, then press Ctrl+C to stop watching.

Pods stuck in ContainerCreating?

The first time you pull a container image it can take a minute depending on your internet speed. Wait 30-60 seconds and the status should change to Running.

If a Pod shows ErrImagePull or ImagePullBackOff, check that Docker Desktop is running and connected to the internet.

help me debug Pods stuck in ContainerCreating or ImagePullBackOff

Inspect the Deployment and ReplicaSet

Your Deployment created a ReplicaSet behind the scenes. A ReplicaSet is the object that actually ensures the correct number of Pods exist at all times.

  • View the Deployment by running:
kubectl get deployment my-web

You should see 3/3 under the READY column, meaning all 3 replicas are healthy.

  • View the ReplicaSet it created by running:
kubectl get replicaset

You should see one ReplicaSet with DESIRED, CURRENT, and READY all showing 3.

  • View detailed information about your Deployment by running:
kubectl describe deployment my-web

What is the Deployment hierarchy?

The hierarchy is Deployment > ReplicaSet > Pods. The Deployment manages the desired state (3 replicas of nginx:1.27).

The ReplicaSet ensures exactly 3 Pods exist at all times. If you scale the Deployment or update its image, Kubernetes handles it through the ReplicaSet layer.

View Pod details

Each Pod has its own lifecycle events. Inspecting a Pod shows how Kubernetes scheduled it and pulled its container image.

  • Pick any Pod name from the output of kubectl get pods.
  • Describe that Pod by running:
kubectl describe pod [[POD_NAME="your Pod name from kubectl get pods"]]

What should I look for?

Scroll down to the Events section at the bottom of the output. It shows the scheduling decision (which node the Pod landed on) and the container image pull.

You will see events like Scheduled, Pulling, Pulled, Created, and Started. These tell the story of how Kubernetes brought your container to life.

Seeing 'not found' when describing a Pod?

Pod names include a random suffix like my-web-5d7f8b6c4-abc12. Copy the full name exactly as shown in kubectl get pods output, including the random characters at the end.

help me find and describe my Pod

Your application is running with 3 healthy replicas. Next, you will expose it to the outside world with a Service so you can actually see it in your browser.

Expose Your App with a Service

Your Deployment has 3 healthy Pods running nginx, but there is no way to send traffic to them from outside the cluster.

Pods get random IP addresses that change every time they restart. A Service gives your Pods a stable address and load-balances traffic across them. In this step, you will create a NodePort Service and access your app in the browser.

In this step, get ready to:
  • Create a NodePort Service that exposes your Deployment.
  • Access your running application in the browser.
  • Understand the four Kubernetes Service types tested on the KCNA.
Create a NodePort Service
  • Expose your my-web Deployment by running this command:
kubectl expose deployment my-web --type=NodePort --port=80

What does this command do?

This creates a Service object named my-web that forwards traffic on port 80 to your Pods.

The --type=NodePort flag tells Kubernetes to also open a high-numbered port (30000-32767) on the node itself. That node port is what makes the app reachable from your machine.

  • View the Service by running:
kubectl get service my-web

Look at the PORT(S) column. It shows something like 80:3xxxx/TCP. The second number is the NodePort that maps external traffic to your app.

Service not showing up?

  • Make sure you typed the Deployment name exactly as my-web. Check with kubectl get deployments to confirm the name.
  • If you see an error about the service already existing, delete it first with kubectl delete service my-web and try again.

Help me troubleshoot my kubectl expose command.

Access your application in the browser

On Windows with the Docker driver, you cannot hit the NodePort directly. Minikube provides a tunnel command that opens the Service URL in your browser automatically.

  • Open the Service in your browser by running:
minikube service my-web

Your default browser opens showing the nginx welcome page. Your app is running on Kubernetes and accessible from your machine.

What is minikube service doing?

Because Minikube runs inside a Docker container on Windows, the node's IP is not directly reachable from your host. The minikube service command creates a temporary tunnel and opens the correct URL so you can view the app in your browser.

Browser not opening?

  • Check that your Minikube cluster is still running with minikube status.
  • If the terminal shows a URL but no browser opens, copy the URL and paste it into your browser manually.
  • Make sure all 3 Pods are still in Running status with kubectl get pods.

Why isn't minikube service opening my app?

Understand Service types (KCNA exam topic)
  • View the full details of your Service by running:
kubectl describe service my-web

Look at the Endpoints field. It lists the IP addresses of all 3 Pods. The Service load-balances traffic across them.

What are the four Kubernetes Service types?

The KCNA tests you on these four Service types:

  • ClusterIP (default): internal only. Pods inside the cluster can reach each other, but nothing outside can.
  • NodePort: exposes the Service on a port on each node. This is what you just created.
  • LoadBalancer: provisions a cloud load balancer in front of your nodes. Not available on a local cluster.
  • ExternalName: maps the Service to an external DNS name without proxying traffic.

Your app is live and reachable from your browser. Next up, you will inject custom configuration with a ConfigMap and watch Kubernetes heal itself when you break things.

Configure with ConfigMaps and Observe Self-Healing

Your app is live and accessible through a NodePort Service. Anyone on your machine can visit it in the browser.

But the app still shows the generic nginx page. In this step, you will inject custom configuration using a ConfigMap and then witness Kubernetes self-healing in action.

In this step, get ready to:
  • Create a ConfigMap containing a custom HTML page.
  • Update the Deployment to mount the ConfigMap into your Pods.
  • Delete a Pod and watch Kubernetes replace it automatically.
Create a ConfigMap with a custom nginx page

Right now your Pods serve the default nginx welcome page. To customize what your app displays, you need to inject new content without rebuilding the container image.

A ConfigMap stores configuration data as key-value pairs. You can mount a ConfigMap into a Pod as a file, letting you change app behavior without touching the image itself.

  • Create a ConfigMap containing a custom HTML page by running this command:
kubectl create configmap my-web-content --from-literal=index.html="<html><body><h1>Hello from Kubernetes!</h1><p>KCNA prep is going great.</p></body></html>"

What does this command do?

This creates a ConfigMap named my-web-content with a single key called index.html.

The value of that key is the HTML string you provided. When mounted into a Pod, Kubernetes writes this key as a file named index.html inside the container.

  • Verify the ConfigMap was created by running:
kubectl get configmap my-web-content -o yaml

You should see the data section containing your index.html key with the HTML content you provided.

Seeing an "already exists" error?

If you ran the command twice, the ConfigMap already exists. Delete it first with kubectl delete configmap my-web-content and then re-run the create command.

Help me fix my ConfigMap creation error.

Update the Deployment to mount the ConfigMap

Your ConfigMap exists, but it is not connected to your Pods yet. You need to update your Deployment to mount the ConfigMap as a volume inside each container.

This requires a declarative YAML manifest. Up until now you have used imperative kubectl commands. YAML manifests let you define the full desired state of a resource in a file.

  • Create a new file called deployment.yaml in your current directory. Open it in any text editor and paste the following content:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-web
  template:
    metadata:
      labels:
        app: my-web
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      volumes:
      - name: html-volume
        configMap:
          name: my-web-content

What does this YAML do?

  • replicas: 3 tells Kubernetes to always keep 3 Pods running.
  • volumeMounts mounts a volume named html-volume at /usr/share/nginx/html inside the container. This is the directory nginx serves files from.
  • volumes defines where html-volume gets its data. It points to your my-web-content ConfigMap.
  • The result: every Pod gets the index.html from your ConfigMap as its web page.
  • Save deployment.yaml.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before applying it.

ⓧ I'd like to double check the full code

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-web
  template:
    metadata:
      labels:
        app: my-web
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      volumes:
      - name: html-volume
        configMap:
          name: my-web-content
  • Apply the manifest to your cluster by running:
kubectl apply -f deployment.yaml
  • Wait for the rollout to finish by running:
kubectl rollout status deployment/my-web

You should see a message indicating the deployment was successfully rolled out.

  • Open your app in the browser by running:
minikube service my-web

You should now see "Hello from Kubernetes!" and "KCNA prep is going great." instead of the default nginx welcome page.

Still seeing the default nginx page?

Make sure you saved deployment.yaml before running kubectl apply. If the file was empty or unsaved, the old Deployment stays in place.

Check that kubectl rollout status deployment/my-web completed successfully. If it is still waiting, run kubectl get pods to check for Pods stuck in a non-Running state.

Help me troubleshoot why my custom page is not showing.

Test self-healing by deleting a Pod

Kubernetes constantly compares the actual state of your cluster with the desired state you declared. If a Pod disappears, the controller creates a replacement to restore the count.

This is the core concept behind container orchestration. Let's break something on purpose and watch Kubernetes fix it.

  • List your current Pods by running:
kubectl get pods

You should see 3 Pods, all with STATUS Running. Note one of the Pod names from the NAME column.

  • Delete one of the Pods by running:
kubectl delete pod [[PODNAME="one of the Pod names from the list above"]]
  • Immediately check the Pods again by running:
kubectl get pods

You will see a new Pod already being created to replace the one you deleted. Within seconds, you are back to 3 running Pods. One of them has a much younger AGE than the others.

Why did Kubernetes create a new Pod?

Your Deployment declares the desired state: 3 replicas. When you deleted a Pod, the actual state dropped to 2. The ReplicaSet controller detected the mismatch and immediately created a replacement.

This happens without any human intervention. In production, this means your app recovers automatically from crashes, node failures, and hardware issues.

Not seeing a new Pod appear?

Make sure you run kubectl get pods immediately after the delete command. If you wait too long, the replacement Pod will already show STATUS Running and look identical to the others. Check the AGE column to spot the newer Pod.

Help me understand what happened when I deleted my Pod.

Secret mission

Deploy a Multi-Tier App with Service DNS Discovery

In production, applications rarely run alone. They communicate with other services behind the scenes. Deploy a second application tier and connect your services using Kubernetes internal DNS discovery, the same pattern used in real microservices architectures.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing cloud costs. Your Minikube cluster does use some RAM and CPU while running inside Docker, so decide whether to keep it running, pause it, or delete it entirely.

Resources you used:

  • Minikube cluster (running inside Docker Desktop, contains all Kubernetes objects).
  • Local YAML files: deployment.yaml and backend-deployment.yaml.

Keep everything running

No action needed. Choose this if you are still actively experimenting with your cluster or plan to continue the KCNA prep series.

  • Your Minikube cluster stays running inside Docker Desktop and uses some RAM and CPU.
  • Your YAML files remain on your Desktop.
  • Minikube and kubectl remain installed on your machine.

Pause - I'll come back to this later

Stop the cluster to free up RAM and CPU without deleting your configuration. You can restart it later.

  • Stop the Minikube cluster by running:
minikube stop
  • When you want to resume, start it again by running:
minikube start

All your Deployments, Services, and ConfigMaps will still be there when you restart.

Delete - I don't want to use this again

Remove the cluster and all local files to start completely fresh.

  • Delete the Minikube cluster by running:
minikube delete
  • Delete your local YAML files by running:
Remove-Item deployment.yaml, backend-deployment.yaml

This removes the cluster VM, all associated Kubernetes objects, and your manifest files. Minikube and kubectl remain installed in case you need them for future projects.

Nice Work!

Nice Work!

You just deployed, configured, and self-healed an application on a local Kubernetes cluster. That is hands-on experience with the core objects that make up 44% of the KCNA exam.

Here's what you learned:

  • Created a Deployment with 3 replicas and observed self-healing in action when Kubernetes automatically replaced a deleted Pod.
  • Exposed your app to the browser with a NodePort Service and injected custom HTML into your Pods using a ConfigMap.
  • Explored the control plane components (API Server, etcd, Scheduler, Controller Manager) and understood the Deployment > ReplicaSet > Pod hierarchy.
  • Secret Mission: Deployed a backend tier and verified DNS-based Service discovery between applications using Kubernetes internal DNS.

Ready to quiz yourself?