Deploy Your First App on Kubernetes

Run a containerized app on a local Kubernetes cluster with Minikube.

Introduction

30 Second Summary

The apps you use every day never seem to go down. Behind the scenes, systems are automatically restarting crashed processes and routing users to healthy copies.

In this project, you will set up a local Kubernetes cluster on your machine, deploy a web server into it, and scale it to multiple copies that share incoming traffic.

What You'll Build

You will have a web server running inside your own Kubernetes cluster that you can pull up in your browser, scale to three replicas with a single command, and watch as each page refresh lands on a different copy of your app.

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

  • A local Kubernetes cluster powered by Minikube, where you deploy and manage a containerized web server straight from your terminal.
  • A Service that connects your browser to your app inside the cluster, solving the isolation problem you discover firsthand.
  • Multiple replicas of your app running at once, with Kubernetes distributing traffic between them automatically.
  • Secret Mission: Write a YAML manifest file to deploy your app declaratively, the way production environments are managed.

Are there any prerequisites?

This project is built for Windows 11 with VS Code already installed. All other tools, including Docker Desktop, Minikube, and kubectl, are installed in Step 1.

Before We Start

Before we dive in, let's lock in what you are about to build and why it matters.

Install Docker Desktop, Minikube, and kubectl

To run Kubernetes on your local machine, you need three tools working together. Docker Desktop provides the container runtime that powers everything underneath. Minikube uses that runtime to create a local Kubernetes cluster on your machine.

The third tool, kubectl, is the command-line tool that lets you talk to your cluster. In this step, you will install all three on your Windows 11 machine and verify each one is working.

In this step, get ready to:
  • Install Docker Desktop and confirm it is running.
  • Install Minikube using the Windows Package Manager.
  • Install kubectl and verify all three tools are ready.
Install Docker Desktop

Docker Desktop gives your machine the ability to run containers. Minikube needs it as the engine that powers your Kubernetes cluster.

What will the installer ask?

The installer will prompt you for two choices. First, it asks for the installation mode. Choose the per-user installation (the default). This does not require administrator privileges.

Second, it asks you to configure the backend on the Configuration page. Make sure Use WSL 2 instead of Hyper-V is selected. This is the default and recommended option for Windows 11.

  • Follow the installer prompts and wait for the installation to complete.
  • Press the Windows key to open your search bar.
  • Type Docker Desktop and press Enter to open it.
  • Wait for Docker Desktop to finish starting. You should see a green "running" indicator in the bottom-left corner of the Docker Desktop window.

What is WSL 2?

WSL 2 stands for Windows Subsystem for Linux version 2. It lets Windows run a full Linux kernel inside a lightweight virtual machine. Docker Desktop uses WSL 2 to run Linux containers on your Windows machine without needing a separate Linux installation.

✔️ I see the green running status

Docker Desktop is ready. You are all set to move on to the next tool.

ⓧ Docker Desktop won't start

Docker Desktop requires WSL 2 to be enabled on your machine. Open PowerShell and run the following command to install WSL:

wsl --install

Restart your computer after this command completes. Then launch Docker Desktop again and wait for the green running indicator.

If Docker Desktop still does not start, make sure hardware virtualization is enabled in your BIOS/UEFI settings. Check your computer manufacturer's documentation for how to access BIOS settings.

Install Minikube

Minikube creates a single-node Kubernetes cluster right on your local 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?

The winget install command uses the Windows Package Manager to download and install software packages. Kubernetes.minikube is the official package ID for Minikube. You will see progress messages as winget downloads the installer and runs it.

  • Close the PowerShell window completely.
  • Press the Windows key, type PowerShell, and press Enter to open a fresh terminal. This refreshes your PATH so Windows can find the newly installed minikube command.

Before you run the next command, what do you expect to see if Minikube installed correctly?

  • Verify the installation by running this command:
minikube version

✔️ I see a version number

Minikube is installed and ready. You should see a line like minikube version: v1.38.1 (your version may be slightly different). Move on to the next tool.

ⓧ Command not found

This usually means the PATH was not refreshed. Make sure you closed and reopened the PowerShell window after installing Minikube. Do not just open a new tab. Close the entire window and open a fresh one.

If it still does not work, try reinstalling by running:

winget install Kubernetes.minikube

Then close and reopen PowerShell again.

Install kubectl

The kubectl command-line tool is how you send instructions to your Kubernetes cluster. Every time you deploy, inspect, or scale an app, you will use kubectl.

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

What does this command do?

This command installs the official Kubernetes command-line client. The -e flag means "exact match" so winget installs the exact package ID Kubernetes.kubectl without ambiguity.

  • Close the PowerShell window completely.
  • Press the Windows key, type PowerShell, and press Enter to open a fresh terminal.

Before you run the next command, what do you expect to see if kubectl installed correctly?

  • Verify the installation by running this command:
kubectl version --client

✔️ I see the client version

kubectl is installed and ready. You should see output that includes Client Version followed by a version number. All three tools are now installed.

ⓧ Command not found

Make sure you closed and reopened the PowerShell window after installing kubectl. Close the entire window and open a fresh one.

If it still does not work, try reinstalling by running:

winget install -e --id Kubernetes.kubectl

Then close and reopen PowerShell again.

Docker Desktop also installs kubectl

Docker Desktop ships its own version of kubectl. If you see version conflicts when running kubectl version --client, the version installed by winget takes priority as long as you reopened your terminal after installing it.

Still seeing errors?

If any of the three verification commands are not working, double-check these common causes:

  • You must fully close and reopen PowerShell after each winget install command. Opening a new tab is not enough.
  • Make sure Docker Desktop is running (green indicator visible) before moving to the next step. Minikube depends on it.
  • If winget itself is not recognized, open the Microsoft Store, search for App Installer, and install or update it.

Ask for help: why aren't my minikube or kubectl commands being recognized in PowerShell after installing with winget?

All three tools are locked and loaded. Next up, you will start your very own Kubernetes cluster and deploy an app into it.

Start Your Cluster and Deploy Your First App

Your tools are installed and ready to go. Now it is time to bring Kubernetes to life on your machine.

In this step, you will spin up a local cluster with Minikube and deploy your first web server into it. By the end, you will have a running application inside Kubernetes that you can inspect and query.

In this step, get ready to:
  • Start a local Kubernetes cluster with Minikube.
  • Deploy a web server as your first Kubernetes application.
  • Inspect your running Pod to confirm everything is working.
Start a Minikube cluster

Minikube creates a single-node Kubernetes cluster running inside a Docker container on your machine. This gives you a fully functional cluster without needing any cloud infrastructure.

  • In your PowerShell terminal, start the cluster by running this command:
minikube start --driver=docker

What does this command do?

The --driver=docker flag tells Minikube to run your cluster inside a Docker container. This is the simplest setup on Windows because Docker Desktop with WSL 2 is already handling the heavy lifting.

The first start downloads several container images, so it may take 2-3 minutes. Future starts will be much faster.

  • When the command finishes, verify your cluster is healthy by running:
minikube status

You should see host, kubelet, and apiserver all showing Running.

Cluster not starting?

  • Make sure Docker Desktop is running and showing a green "running" status. Minikube needs Docker to create the cluster.
  • If you see a resource error, check that your machine has at least 2 CPUs and 2 GB of free memory available.
  • Help me troubleshoot why minikube start is failing.
Deploy a web server

A Deployment is the standard way to run an application in Kubernetes. It tells Kubernetes which container image to use and how many copies of your app to keep running.

  • Create a Deployment called hello-k8s by running this command:
kubectl create deployment hello-k8s --image=registry.k8s.io/e2e-test-images/agnhost:2.53 -- /agnhost netexec --http-port=8080

What does this command do?

  • It creates a Deployment named hello-k8s in your cluster.
  • The --image flag specifies the container image to run. This image is an official Kubernetes test server called agnhost.
  • The netexec --http-port=8080 part tells the container to start an HTTP server on port 8080 that echoes back request details like headers and the hostname of the Pod.
  • Check that your Deployment was created by running:
kubectl get deployments

You should see hello-k8s listed with the READY column showing 1/1.

  • Check the Pod that your Deployment created by running:
kubectl get pods

You should see one Pod with a name starting with hello-k8s- and STATUS showing Running. If the STATUS shows ContainerCreating, wait a few seconds and run the command again.

What is a Pod?

A Pod is the smallest unit in Kubernetes. It wraps one or more containers and gives them a shared network address.

When you created the Deployment, Kubernetes automatically created a Pod to run your container. The Deployment watches that Pod and restarts it automatically if it ever crashes.

Pod stuck in ContainerCreating or ErrImagePull?

  • If the Pod stays in ContainerCreating for more than a minute, the image may still be downloading. Run kubectl get pods again to check for progress.
  • If you see ErrImagePull, check that your machine has an active internet connection. Kubernetes needs to download the container image from the registry.
  • Help me troubleshoot my Pod status.
Inspect your running Pod

Your Pod is running, but let's look under the hood to confirm the HTTP server inside it started correctly.

  • Copy your Pod name from the kubectl get pods output above. It looks something like hello-k8s-abcdef1234-xyz56. Record it here: your Pod name from kubectl get pods
  • Get detailed information about your Pod by running:
kubectl describe pod [[POD_NAME="your Pod name from kubectl get pods"]]

What should I see?

The output is a detailed report of your Pod's life. Scroll down to the Events section at the bottom.

You should see entries for Scheduled, Pulling, Pulled, Created, and Started. This tells the story of how Kubernetes assigned the Pod to a node, downloaded the image, and launched the container.

Before you check the application logs, what do you think the container has been doing since it started?

  • View the container's application logs by running:
kubectl logs [[POD_NAME="your Pod name from kubectl get pods"]]

You should see a line confirming the HTTP server started on port 8080. This proves your application is alive and ready to serve requests inside the cluster.

Not seeing any log output?

  • Double check that you replaced the placeholder with your actual Pod name from kubectl get pods.
  • If the Pod is not yet in Running status, the container has not started and there are no logs to show. Wait and re-check with kubectl get pods.
  • Help me troubleshoot kubectl logs.

Your first app is alive and running inside your Kubernetes cluster. But can you actually reach it from your browser? In the next step, you will try to access your app and discover why Kubernetes keeps Pods hidden from the outside world.

Try to Reach Your App (and Fail)

Your hello-k8s app is deployed and running inside the cluster. The Pod shows "Running" and the logs confirm the HTTP server is active on port 8080.

If your app is running, surely you can open it in a browser and see it working, right? In this step, you will try to find a way in and discover something important about how Kubernetes networking works.

In this step, get ready to:
  • Run kubectl get services to check if your app has a network entry point.
  • Run kubectl get pods -o wide to inspect your Pod's internal IP address and understand why it is unreachable.
Check the available services

In Kubernetes, a Service is a stable network endpoint that routes traffic to your Pods. Without one, there is no door from the outside world into the cluster.

Before you run the next command, think about this: your app is running. Do you think Kubernetes has automatically created a network entry point for it?

  • Check what Services exist in your cluster by running this command:
kubectl get services

You will see output like this:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   10m

There is only one entry: the default kubernetes ClusterIP service. Your hello-k8s app is nowhere on that list. Kubernetes did not automatically create a way for outside traffic to reach your Pod.

Why is there no Service for my app?

When you created the Deployment in Step 2, Kubernetes created a Pod to run your container. But it did not create a Service to expose that Pod to the outside world.

This is by design. Pods are meant to be internal workers. You decide which ones get exposed and how.

Seeing a connection error?

  • If kubectl cannot connect to the cluster, make sure Docker Desktop is still running and shows the green "running" indicator.
  • Try running minikube status to confirm the cluster is still healthy. All three lines (host, kubelet, apiserver) should say "Running".

Why can't kubectl connect to my cluster?

Understand why the Pod is unreachable

Your Pod does have an IP address. But that address only works inside the cluster's private network.

  • See the Pod's full network details by running this command:
kubectl get pods -o wide

You will see the same Pod information as before, but now with extra columns. Look at the IP column. It shows an address like 10.244.0.x.

Why can't I use this IP in my browser?

That 10.244.0.x IP address exists only inside the Kubernetes cluster network. Your browser runs outside that network, so it has no route to reach it.

Pods are also ephemeral. Kubernetes can destroy and replace a Pod at any time, and the replacement gets a brand new IP. Even if you could reach the Pod's IP, it would stop working the moment Kubernetes replaces the Pod.

This is exactly why Services exist. A Service gives you a stable entry point that survives Pod replacements.

Now you know the gap. Your app is alive and healthy inside the cluster, but there is no door to let outside traffic in. In the next step, you will create that door by building a Kubernetes Service.

Expose Your App with a Service

In the last step, you discovered that your running app is completely invisible to the outside world. The Pod's internal IP only works inside the Kubernetes cluster network.

A Service is the Kubernetes solution to this isolation problem. It creates a stable network endpoint that routes traffic to your Pod, even if the Pod gets replaced.

In this step, get ready to:
  • Create a LoadBalancer Service that opens a door from the outside world into your cluster.
  • Access your running app in the browser through Minikube's tunneling feature.
Create a LoadBalancer Service

Right now, kubectl get services only shows the default kubernetes ClusterIP service. You need to create a Service specifically for your hello-k8s Deployment so outside traffic can reach it.

  • Create a LoadBalancer Service for your hello-k8s Deployment by running this command:
kubectl expose deployment hello-k8s --type=LoadBalancer --port=8080

What does this command do?

  • kubectl expose deployment hello-k8s creates a Service that fronts the hello-k8s Deployment, giving it a stable network address.
  • --type=LoadBalancer tells Kubernetes to provision an external entry point so traffic from outside the cluster can get in.
  • --port=8080 maps incoming traffic to port 8080 on your Pod, which is where the app is listening.
  • Confirm the Service was created by running:
kubectl get services

You should now see a hello-k8s entry with TYPE LoadBalancer in the output. The EXTERNAL-IP column will show <pending>.

Why is EXTERNAL-IP pending?

On a cloud provider like AWS or Azure, a LoadBalancer Service would automatically get a public IP address. Minikube runs locally, so it handles external access differently.

Instead of assigning an IP, Minikube uses the minikube service command to create a tunnel from your machine into the cluster. You will use this in the next substep.

Service not appearing?

  • Make sure you typed the deployment name hello-k8s exactly. It must match the Deployment you created in Step 2.
  • Run kubectl get deployments to confirm the Deployment still exists. If it is missing, go back to Step 2 and recreate it.
  • Why isn't my Kubernetes Service appearing after running kubectl expose?
Access your app through Minikube

The Service is created, but the <pending> EXTERNAL-IP means you cannot just open an IP in your browser. Minikube provides a shortcut that creates a tunnel and opens the app for you.

Before you run the next command, what do you think you will see in your browser? Will it be a blank page, an error, or something from your app?

  • Open your app in the browser by running:
minikube service hello-k8s

Your default browser opens automatically. You should see a page displaying request information including headers, the request URI, and a hostname.

What is the hostname in the response?

The hostname displayed on the page is the name of the Pod that handled your request. It will look something like hello-k8s-abc123-xyz.

Right now you only have one Pod, so the hostname stays the same on every refresh. In the next step, you will scale to multiple replicas and watch this hostname change.

Why does this command block the terminal?

Because you are using the Docker driver on Windows, the minikube service command creates a tunnel that must stay active. The terminal needs to remain open for the tunnel to keep working.

If you need to run other commands, open a new PowerShell window. Do not close the terminal running the tunnel.

Browser not opening?

  • Look at the terminal output for a URL (it will look like http://127.0.0.1: followed by a port number). Copy that URL and paste it into your browser manually.
  • Make sure Docker Desktop is still running. The Minikube tunnel depends on it.
  • Help me troubleshoot why minikube service is not opening my app in the browser.

Your app is now reachable from outside the cluster. Next up, you will scale it to multiple replicas and watch Kubernetes distribute traffic across them automatically.

Scale Your App and See Load Balancing

Your app is live and reachable through its Service. But right now, only one copy of your app is running inside the cluster.

If that single Pod crashes, your app goes down entirely. In this step, you will scale your Deployment to multiple replicas and watch Kubernetes distribute traffic across them automatically.

In this step, get ready to:
  • Scale your Deployment to 3 replicas.
  • Verify that Kubernetes load-balances traffic across all replicas.
  • Scale back down to 1 replica.
Scale to 3 replicas

Scaling a Deployment means telling Kubernetes to run more copies of your app. Each copy runs in its own Pod, so if one Pod goes down, the others keep serving traffic.

  • Scale your Deployment to 3 replicas by running this command:
kubectl scale deployment/hello-k8s --replicas=3

What does this command do?

kubectl scale changes the number of Pods a Deployment manages.

--replicas=3 tells Kubernetes to ensure exactly 3 copies of your app are running at all times. Kubernetes creates the missing Pods to match the count you specified.

  • Check on your new Pods by running:
kubectl get pods

You should see three Pods listed. They may briefly show STATUS ContainerCreating before all three switch to Running.

  • Confirm all replicas are ready by running:
kubectl get deployments

The READY column shows 3/3, meaning all three replicas are up and healthy.

Pods stuck in ContainerCreating?

  • Wait 30 seconds and run kubectl get pods again. Pulling the container image can take a moment, especially the first time.
  • Make sure Docker Desktop is still running. Kubernetes needs a working container runtime to start new Pods.
  • Why are my Pods stuck in ContainerCreating?
Verify load balancing across your replicas

With three replicas running, Kubernetes automatically distributes incoming requests across all of them. This is load balancing in action.

Before you check the browser, what do you think the Hostname field in the response will show now that three Pods are handling requests?

  • Return to the browser tab that opened in the previous step and refresh the page.

Browser tab closed?

If you closed the browser tab, reopen your app by running minikube service hello-k8s in your terminal.

  • Refresh the page several times and watch the Hostname field in the response.

The hostname changes between different Pod names with each refresh. Kubernetes is distributing requests across your three replicas automatically.

  • See which Pod IPs your Service routes to by running:
kubectl describe service hello-k8s

What should I see?

Look at the Endpoints field. It lists three IP addresses separated by commas, one for each Pod replica.

Your Service uses these endpoints to know where to send traffic. When you refresh the browser, the Service picks one of the three Pods to handle the request.

Hostname not changing when you refresh?

  • Try a hard refresh. Press Ctrl+Shift+R to bypass the browser cache.
  • Run kubectl get pods and confirm all three Pods show STATUS Running. If any Pod is not ready, requests will not be routed to it.
  • Why isn't the hostname changing when I refresh the page?
Scale back down to 1 replica

Scaling is a reversible operation. One command brings you back down, just as easily as one command scaled you up.

What do you think will happen to the extra two Pods when you scale back down to 1?

  • Scale your Deployment back to a single replica by running:
kubectl scale deployment/hello-k8s --replicas=1
  • Check the result by running:
kubectl get pods

Two Pods show STATUS Terminating briefly before disappearing from the list. One Pod remains Running.

What just happened?

Kubernetes gracefully shut down the two extra Pods. It sent them a termination signal, waited for them to finish handling any in-progress requests, then removed them.

Scaling up and down is this simple in production too. Teams scale up during peak traffic and scale back down when demand drops.

Still seeing 3 Pods?

  • Wait a few seconds and run kubectl get pods again. Termination takes a moment as Kubernetes waits for containers to shut down gracefully.
  • Run kubectl get deployments to check that the READY column shows 1/1. If it does, the scale-down succeeded and the old Pods are still cleaning up.
  • Why are my Pods not terminating after scaling down?

Secret mission

Deploy with a YAML Manifest

Throughout this project, you used imperative commands like kubectl create and kubectl scale to manage your app. In production, teams manage Kubernetes declaratively through YAML manifest files. Your mission: delete your imperative resources and rewrite the entire deployment as a single YAML file that Kubernetes can apply in one command.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on your local machine, so there are no ongoing cloud costs.

Resources you used:

  • A Minikube cluster running as a Docker container, containing your hello-k8s Deployment and Service.
  • The hello-k8s.yaml manifest file on your local machine.
  • Docker Desktop, Minikube, and kubectl installed on your Windows machine.

Keep everything running

No action needed. Choose this if you are still actively experimenting with your Kubernetes cluster.

  • Your Minikube cluster and all resources inside it will keep running.
  • Docker Desktop will continue using system resources (CPU, memory) in the background.
  • Your hello-k8s.yaml file stays on your machine for future use.

Pause - I'll come back to this later

Shut down your cluster to free up CPU and memory while keeping your configuration preserved.

  • Close any browser tabs that were opened by minikube service.
  • Stop your Minikube cluster by running:
minikube stop
  • Your cluster configuration and resources are preserved. Restart anytime by running minikube start.
  • Optionally, right-click the Docker Desktop icon in the system tray and select Quit Docker Desktop to free additional memory.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Close any browser tabs that were opened by minikube service.
  • Delete the Minikube cluster and all its resources by running:
minikube delete
  • Delete the hello-k8s.yaml file by navigating to it in File Explorer and deleting it. Alternatively, run the following command in the folder where you created it:
Remove-Item hello-k8s.yaml
  • To uninstall Minikube and kubectl, open a PowerShell terminal and run:
winget uninstall Kubernetes.minikube
winget uninstall --id Kubernetes.kubectl
  • To uninstall Docker Desktop, open Settings on your Windows machine.
  • Select Apps, then Apps & features.
  • Find Docker Desktop in the list and select Uninstall.
  • Confirm the uninstallation when prompted.

Nice Work!

Nice Work!

Nice work! You just deployed a web application to your own local Kubernetes cluster, exposed it to the outside world, and scaled it across multiple replicas.

Here's what you learned:

  • Set up a complete local Kubernetes environment from scratch with Docker Desktop, Minikube, and kubectl on Windows 11, then deployed a containerized web app using a Deployment.
  • Discovered that Pods are isolated by default, then created a LoadBalancer Service to open a door from your browser into the cluster.
  • Scaled your app to multiple replicas and watched Kubernetes automatically load-balance traffic across them.
  • Secret Mission: Completed an optional challenge to push your Kubernetes skills further.

Ready to quiz yourself?