Deploy Your First App on Kubernetes
Deploy a multi-replica web app on a local Kubernetes cluster with Minikube.
Introduction
30 Second Summary
Every app you use needs to stay running. When a server crashes or traffic spikes, something needs to automatically replace broken parts and spread the load. That something is Kubernetes, the container orchestration tool that powers most production applications today.
In this project, you will deploy a web application on a local Kubernetes cluster using Minikube. You'll then put Kubernetes through its paces by deleting pods, scaling replicas, and performing rolling updates to see how it manages your app automatically.
What You'll Build
A fully functional local Kubernetes environment running a multi-replica web application that you deploy, scale, update, and roll back using kubectl commands.
By the end of this project, you'll have:
- A running Minikube cluster on your Windows machine using Docker as the driver.
- A containerized web app deployed with 3 replicas and exposed via a Kubernetes Service, accessible in your browser.
- Hands-on experience with Kubernetes self-healing, scaling, rolling updates, and rollbacks.
- Secret Mission: Add a Horizontal Pod Autoscaler that automatically scales your app based on CPU usage.
Are there any prerequisites?
You should have Docker Desktop installed on Windows and a basic familiarity with the command line. No prior Kubernetes experience is needed.
Install Minikube and kubectl
Before you can deploy anything to Kubernetes, you need two command-line tools installed on your machine.
The first is kubectl, which lets you send instructions to any Kubernetes cluster. The second is Minikube, which creates a full Kubernetes cluster right on your laptop using Docker.
In this step, get ready to:
- Install kubectl and verify it works.
- Install Minikube and verify it works.
- Confirm Docker Desktop is set to Linux containers.
Install kubectl
kubectl is the command-line tool that sends instructions to a Kubernetes cluster. Every action you take in this project goes through kubectl.
- In your PowerShell window, install kubectl by running this command:
winget install -e --id Kubernetes.kubectl
What does this command do?
winget is the Windows Package Manager. The -e flag ensures an exact match on the package ID, and --id Kubernetes.kubectl specifies the official kubectl package from the winget repository.
- Close your PowerShell window completely.
- Reopen PowerShell by pressing the Windows key, typing PowerShell, and pressing Enter.
Why do I need to reopen PowerShell?
The winget installer adds kubectl to your system PATH. PowerShell only reads PATH when it first opens, so you need a fresh window for it to find the new tool.
- Verify kubectl is installed by running:
kubectl version --client
✔️ I see a version number
kubectl is installed and ready. You can move on to installing Minikube.
ⓧ Command not found
PowerShell can't find kubectl on your PATH. Try these fixes:
- Make sure you closed and reopened PowerShell after the winget install completed.
- Run the install command again to ensure it completed successfully.
- If you still see the error after reopening, restart your computer to force a full PATH refresh.
What about Docker Desktop's kubectl?
Docker Desktop may have installed its own version of kubectl. The winget installation gives you the latest standalone version. If you see any version output from the command above, you're good to go.
Install Minikube
Minikube creates a single-node Kubernetes cluster inside a Docker container on your machine. It gives you a real cluster to practice on without needing cloud infrastructure.
- Install Minikube by running this command:
winget install Kubernetes.minikube
- Close your PowerShell window completely.
- Reopen PowerShell by pressing the Windows key, typing PowerShell, and pressing Enter.
- Verify Minikube is installed by running:
minikube version
✔️ I see version v1.38.1 or newer
Minikube is installed and ready. You should see output like minikube version: v1.38.1 or a higher number.
ⓧ I see an older version
You have a previous installation of Minikube that's outdated. Upgrade it by running:
winget upgrade Kubernetes.minikube
- Close and reopen PowerShell after the upgrade completes.
- Run minikube version again to confirm you see v1.38.1 or newer.
ⓧ Command not found
PowerShell can't find the Minikube executable. Try these fixes:
- Make sure you closed and reopened PowerShell after the winget install completed.
- Run the install command again to ensure it completed successfully.
- If you still see the error after reopening, restart your computer to force a full PATH refresh.
Verify Docker Desktop is ready
Minikube uses Docker Desktop to create your cluster. It specifically needs Docker running in Linux containers mode, not Windows containers mode.
- Confirm Docker is using Linux containers by running:
docker info --format "{{.OSType}}"
✔️ I see linux
Docker Desktop is configured correctly for Minikube. You're ready to start your Kubernetes cluster.
ⓧ I see windows
Docker Desktop is running in Windows containers mode. You need to switch it to Linux containers:
- Right-click the Docker Desktop icon in your system tray (bottom-right of your taskbar).
- Select Switch to Linux containers.
- Wait for Docker to restart, then run the docker info command again to confirm it now outputs linux.
Your tools are locked and loaded. Next up, you'll use Minikube to spin up a real Kubernetes cluster on your machine.
Start Your Kubernetes Cluster
You've installed Minikube and kubectl. Now it's time to spin up your own Kubernetes cluster right on your machine.
Minikube creates a single-node Kubernetes cluster inside a Docker container. This gives you a full Kubernetes environment to practice with, no cloud resources required.
In this step, get ready to:
- Start your local Kubernetes cluster with Minikube.
- Verify the cluster is healthy using kubectl.
- Explore the Kubernetes Dashboard.
Start Minikube with the Docker driver
A Kubernetes cluster is the foundation everything else runs on. Minikube uses Docker Desktop to create a container that acts as your Kubernetes node.
- Start your local Kubernetes cluster by running this command in PowerShell:
minikube start --driver=docker
This takes 1-2 minutes the first time as it downloads the Kubernetes components. You'll see progress messages as Minikube pulls the base image, creates the control-plane node, and boots up the cluster.
When it finishes, you should see a message that says Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default.
What does this command do?
The --driver=docker flag tells Minikube to use Docker Desktop as the virtualization layer. Instead of creating a full virtual machine, Minikube runs your Kubernetes node inside a Docker container. This is faster and lighter on resources.
Seeing an error about Docker?
Make sure Docker Desktop is still running. Check the system tray for the Docker whale icon.
If you see an error about the container runtime, confirm Docker is set to Linux containers (not Windows containers) as you verified in the previous step.
help me fix a Minikube start error with the Docker driver on Windows
Explore your cluster
Your cluster is running. Now confirm everything is healthy and see what Kubernetes created for you.
- Verify your cluster status by running these commands:
minikube status
kubectl cluster-info
kubectl get nodes
What do these commands show?
- minikube status shows whether the host, kubelet, and apiserver are all Running.
- kubectl cluster-info shows the URL where the Kubernetes control plane is accessible.
- kubectl get nodes lists all nodes in your cluster. You should see one node named minikube with STATUS Ready.
This single node is your entire cluster for learning purposes. In production, clusters typically have many nodes, but the concepts are the same.
Node showing NotReady?
If the node status shows NotReady, wait 30 seconds and run kubectl get nodes again. Kubernetes components may still be initializing.
help me troubleshoot why my Minikube node shows NotReady status
Open the Kubernetes Dashboard (optional)
Minikube includes a built-in web dashboard for visualizing everything happening inside your cluster. This is a great way to see pods, deployments, and services as you create them in the next steps.
- Open the dashboard by running this command:
minikube dashboard
What does this open?
This launches your default browser with a visual overview of your cluster. You can see namespaces, workloads, and services in a graphical interface. The command keeps running in your terminal to serve the dashboard.
- Leave the dashboard running in this terminal if you want to use it.
- Open a new PowerShell window for the remaining commands in this project.
- Press Ctrl+C in the dashboard terminal whenever you want to stop it.
Dashboard is optional
The dashboard is helpful for visual learners but not required. Everything in this project can be done entirely from the command line.
Your cluster is up and running. Next up, you'll deploy your first application onto it by writing a Kubernetes manifest from scratch.
Deploy Your Application
Your cluster is up and running with a single node in Ready status. Now it's time to put it to work by deploying an actual application.
Kubernetes uses a resource called a Deployment to manage your application's lifecycle. You'll write a YAML manifest that tells Kubernetes what container image to run, how many copies to keep alive, and which port to expose.
In this step, get ready to:
- Create a Deployment manifest that defines 3 replicas of a web app.
- Apply the manifest to your cluster.
- Verify all 3 pods are running.
Create the Deployment manifest
A Deployment tells Kubernetes how many copies of your application to run and which container image to use. If a copy goes down, the Deployment controller automatically replaces it.
- Create a project folder on your Desktop by running these commands in PowerShell:
cd ~/Desktop
mkdir k8s-project
cd k8s-project
- Confirm your folder was created by running:
ls
You should see an empty k8s-project folder. This is where all your Kubernetes manifest files will live.
- Switch to VS Code.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the k8s-project folder, then click Select Folder.
- Create a new file by clicking File then New File.
- Name the file deployment.yaml and press Enter.
- Paste the following code into deployment.yaml:
# Tells Kubernetes which API group and version to use for this resource
apiVersion: apps/v1
kind: Deployment
metadata:
# The name you'll use to reference this Deployment in kubectl commands
name: hello-kubernetes
labels:
app: hello-kubernetes
spec:
# Run 3 identical copies (pods) of the application
replicas: 3
selector:
# How the Deployment finds which pods it manages
matchLabels:
app: hello-kubernetes
template:
metadata:
# Each pod gets this label so the Deployment can track it
labels:
app: hello-kubernetes
spec:
containers:
- name: hello-kubernetes
# A demo web app that displays the pod name on a web page
image: paulbouwer/hello-kubernetes:1.10
ports:
# The port the container listens on internally
- containerPort: 8080
What does this manifest do?
- replicas: 3 tells Kubernetes to always keep exactly 3 copies (called Pods) of your app running.
- selector and labels are how the Deployment tracks which pods belong to it. They must match.
- image: paulbouwer/hello-kubernetes:1.10 is a demo web app that displays the pod name on a web page. This makes load balancing visible later.
- containerPort: 8080 declares the port the app listens on inside each container.
- Save deployment.yaml by pressing Ctrl+S.
- Confirm you can see deployment.yaml listed in VS Code's Explorer sidebar on the left.
Don't see the file in Explorer?
Make sure you opened the k8s-project folder (not a parent folder) via File > Open Folder. If the Explorer sidebar is hidden, press Ctrl+Shift+E to toggle it.
help me find my deployment.yaml file in VS Code
✔️ Awesome, I've got everything!
Great. Make sure you've saved deployment.yaml before continuing.
ⓧ I'd like to double check the full code
Here is the complete deployment.yaml for comparison:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kubernetes
labels:
app: hello-kubernetes
spec:
replicas: 3
selector:
matchLabels:
app: hello-kubernetes
template:
metadata:
labels:
app: hello-kubernetes
spec:
containers:
- name: hello-kubernetes
image: paulbouwer/hello-kubernetes:1.10
ports:
- containerPort: 8080
Apply the Deployment
Your manifest file describes what you want. Now you'll hand it to Kubernetes so the cluster can make it real.
- Switch back to your PowerShell terminal (which should still be in the k8s-project folder).
- Deploy your application to the cluster by running:
kubectl apply -f deployment.yaml
What does this command do?
kubectl apply -f deployment.yaml sends your manifest to the Kubernetes API server. The server reads your desired state (3 pods running this image) and starts creating the resources to match it.
The -f flag stands for "file" and points kubectl to the manifest you want to apply.
You should see output similar to:
deployment.apps/hello-kubernetes created
Seeing 'error: no objects passed to apply'?
This means PowerShell cannot find deployment.yaml in your current directory. Run ls to check if the file is listed. If not, run cd ~/Desktop/k8s-project and try the apply command again.
help me fix the kubectl apply error
Verify the Deployment
Kubernetes works asynchronously. After you apply a manifest, it takes a few seconds for pods to download the image and reach a running state.
- Check your Deployment and pods by running these commands:
kubectl get deployments
kubectl get pods
What should I see?
kubectl get deployments shows your Deployment with 3/3 in the READY column. This means all 3 desired replicas are running.
kubectl get pods lists each individual pod. You should see 3 pods with STATUS Running. Each pod name starts with hello-kubernetes- followed by a random suffix.
Watch pods in real-time
Run kubectl get pods -w to watch pod status updates live. Press Ctrl+C to stop watching when all 3 pods show Running.
Pods stuck in ContainerCreating or Pending?
This usually means Kubernetes is still pulling the container image. Wait 30-60 seconds and run kubectl get pods again. If a pod shows ErrImagePull, check that Docker Desktop is running and your internet connection is active.
help me fix pods that won't start
You've just deployed your first multi-replica application on Kubernetes. Next up, you'll create a Service to expose your app and see load balancing in action.
Expose Your App with a Service
Your 3 pods are running, but there's a problem. They each have their own internal IP address that changes every time a pod restarts. You have no stable way to reach your app from outside the cluster.
A Service solves this by giving your pods a single, stable network endpoint. It also load-balances traffic across all matching pods automatically. In this step, you'll create a Service that makes your app accessible in your browser.
In this step, get ready to:
- Write a Service manifest that exposes your pods via NodePort.
- Access your running app in the browser and see load balancing in action.
- Verify the Service endpoints point to all 3 pods.
Create the Service manifest
Right now your pods are only reachable inside the cluster using their internal IPs. A NodePort Service opens a specific port on the cluster node so traffic from outside can reach your pods.
- In VS Code, create a new file called service.yaml in the same folder as your deployment.yaml.
- Paste the following YAML into service.yaml:
apiVersion: v1
kind: Service
metadata:
name: hello-kubernetes-svc
spec:
type: NodePort
selector:
app: hello-kubernetes
ports:
- port: 80
targetPort: 8080
nodePort: 30080
What does this manifest do?
This YAML defines a Kubernetes Service with four key pieces:
- type: NodePort makes the app accessible outside the cluster by opening a port on the node itself.
- selector: app: hello-kubernetes tells the Service which pods to route traffic to. It matches pods with the label app: hello-kubernetes from your Deployment.
- targetPort: 8080 is the port your container listens on. port: 80 is the Service's own port. nodePort: 30080 is the external port opened on the node.
- Save service.yaml.
How does selector matching work?
The selector field is the glue between your Service and your pods. In your deployment.yaml, you gave every pod the label app: hello-kubernetes. The Service finds all pods with that label and automatically routes traffic to them.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's the complete service.yaml for comparison:
apiVersion: v1
kind: Service
metadata:
name: hello-kubernetes-svc
spec:
type: NodePort
selector:
app: hello-kubernetes
ports:
- port: 80
targetPort: 8080
nodePort: 30080
Apply the Service and access your app
- Apply your Service to the cluster by running this command in PowerShell:
kubectl apply -f service.yaml
What does this command do?
kubectl apply -f service.yaml sends your Service definition to the cluster. Kubernetes reads the YAML, creates the Service object, and immediately starts routing traffic to pods matching the selector.
You should see the output service/hello-kubernetes-svc created.
- Open your app in the browser by running:
minikube service hello-kubernetes-svc
This command opens your default browser to the app. You'll see a page showing the pod name that handled your request.
- Refresh the page a few times.
Notice the pod name changes on each refresh. The Service is load-balancing your requests across all 3 running pods.
Browser didn't open?
If the browser didn't launch automatically, look at the terminal output. It shows a URL like http://127.0.0.1:XXXXX. Copy that URL and paste it into your browser manually.
If you see a connection refused error, make sure all 3 pods are still in Running status by running kubectl get pods.
help me troubleshoot why my browser can't connect to my Kubernetes Service
Verify the Service
Now that your app is accessible, let's inspect the Service to understand how it connects to your pods behind the scenes.
- Check the Service details by running these commands:
kubectl get services
kubectl describe service hello-kubernetes-svc
What should I see?
kubectl get services lists all Services in your cluster. You should see hello-kubernetes-svc with type NodePort and port 80:30080/TCP.
kubectl describe service hello-kubernetes-svc shows detailed information. Look at the Endpoints field. You'll see 3 IP addresses listed. These are the internal IP addresses of your 3 pods. The Service routes incoming traffic to these endpoints.
Seeing no endpoints?
If the Endpoints field is empty, the Service selector does not match any pods. Check that your service.yaml has selector: app: hello-kubernetes (matching the label in your deployment.yaml). Labels are case-sensitive.
help me figure out why my Kubernetes Service has no endpoints
Your app is deployed, load-balanced, and accessible from your browser. Next up, you'll put Kubernetes through its paces by breaking things on purpose and watching it recover.
Explore Kubernetes Superpowers
Your application is deployed and accessible through a Kubernetes Service. Traffic flows to your 3 pods, and refreshing the browser shows different pod names handling each request.
But what happens when things go wrong? What if a pod crashes, traffic spikes, or you need to ship a new version without downtime? In this step, you'll put Kubernetes through its paces by breaking things on purpose and watching it recover.
In this step, get ready to:
- Witness self-healing by deleting a pod and watching Kubernetes replace it.
- Scale your deployment up and down with a single command.
- Perform a rolling update and rollback with zero downtime.
Self-healing
Your Deployment told Kubernetes to keep exactly 3 pods running at all times. If any pod disappears, the Deployment controller notices the gap and creates a replacement. Let's prove this works.
- List your running pods by running this command:
kubectl get pods
You should see 3 pods listed, each with a unique name like hello-kubernetes-xxxxxxxxxx-xxxxx. Copy one of those full pod names.
- Delete one of your pods on purpose by running this command (paste in the pod name you copied):
kubectl delete pod [[POD_NAME="paste one of your pod names here"]]
- Immediately check your pods again by running:
kubectl get pods
You should see 3 pods again. One has a very recent AGE (just a few seconds old). That's the replacement Kubernetes created automatically.
What just happened?
The Deployment controller constantly compares the actual number of running pods against the desired count (3 replicas). When you deleted a pod, the actual count dropped to 2.
Within seconds, the controller created a new pod to bring the count back to 3. This is self-healing in action. In production, this means crashed containers are replaced automatically without any human intervention.
Still seeing only 2 pods?
Run kubectl get pods again after a few seconds. The replacement pod may still be in ContainerCreating status. If it stays stuck, make sure your Minikube cluster is still running with minikube status.
Help me troubleshoot why my pod isn't being replaced after deletion
Scaling
In production, traffic spikes happen. Kubernetes lets you scale your application up or down instantly with a single command. No rewriting manifests, no redeploying.
- Scale your deployment up to 5 replicas by running this command:
kubectl scale deployment hello-kubernetes --replicas=5
- Verify the new pods are running:
kubectl get pods
You should see 5 pods listed. Kubernetes spun up 2 additional pods within seconds to match your new desired state.
- Now scale back down to 2 replicas by running:
kubectl scale deployment hello-kubernetes --replicas=2
- Check your pods one more time:
kubectl get pods
You should see only 2 pods now. Kubernetes terminated the extra 3 pods to match your desired state of 2 replicas.
Why is scaling this easy?
Kubernetes separates the desired state (what you want) from the actual state (what's running). When you change the replica count, the Deployment controller reconciles the difference. It adds pods to scale up or terminates pods to scale down.
Your Service automatically routes traffic to however many pods exist. No reconfiguration needed.
Pods stuck in Pending or Terminating?
Pending pods usually mean your node doesn't have enough resources. Minikube's single node has limited CPU and memory. Try scaling to a smaller number (like 4) instead of 5. Terminating pods may take 30 seconds to fully shut down as Kubernetes gracefully drains connections.
Help me fix pods that are stuck in Pending or Terminating status
Rolling updates and rollbacks
Shipping a new version of your app in production can't mean taking it offline. Kubernetes handles this with rolling updates. It progressively replaces old pods with new ones, so your app stays available throughout the entire process.
- Update your app to version 1.11 by running this command:
kubectl set image deployment/hello-kubernetes hello-kubernetes=paulbouwer/hello-kubernetes:1.11
- Watch the rollout happen in real time by running:
kubectl rollout status deployment/hello-kubernetes
You should see messages about old pods being terminated and new ones being created. When it finishes, you'll see a message confirming the rollout completed successfully.
What does a rolling update do?
Kubernetes doesn't kill all old pods at once and start new ones. It gradually spins up a new pod with the updated image, waits for it to become ready, then terminates one old pod. This repeats until all pods run the new version.
At no point during this process is your app fully unavailable. This is how production deployments work at scale.
Now imagine the new version has a bug. You need to roll back immediately.
- Check your rollout history by running:
kubectl rollout history deployment/hello-kubernetes
You should see at least 2 revisions listed. Each revision represents a version of your Deployment.
- Roll back to the previous version by running:
kubectl rollout undo deployment/hello-kubernetes
- Confirm the rollback completed by running:
kubectl rollout status deployment/hello-kubernetes
You should see the rollout complete successfully. Your pods are now running the original paulbouwer/hello-kubernetes:1.10 image again.
Why are rollbacks instant?
Kubernetes stores the full configuration for each revision of your Deployment. When you run kubectl rollout undo, it doesn't need to rebuild anything. It simply tells the Deployment controller to use the previous revision's configuration.
This is the same rolling update process in reverse. Old (buggy) pods are replaced with pods running the previous known-good image. Zero downtime in both directions.
Rollout stuck or showing errors?
If kubectl rollout status seems to hang, check your pods with kubectl get pods. Look for pods in ImagePullBackOff status, which means the image name or tag was incorrect. Verify you typed the image name exactly as shown above.
Help me troubleshoot a stuck rollout in my Kubernetes deployment
Secret mission
Add a Horizontal Pod Autoscaler
You've scaled your app manually. But what if Kubernetes could scale it for you? In this secret mission, you'll configure a Horizontal Pod Autoscaler that watches CPU usage and automatically adds or removes pods based on demand. This is the same mechanism production clusters use to handle traffic spikes without human intervention.
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 locally, so there are no ongoing costs beyond the ~2 GB of RAM that Minikube uses while running.
Resources you used:
- Minikube cluster (running as a Docker container on your machine).
- Local project files (deployment.yaml, hpa.yaml, service.yaml).
- Minikube (installed via winget).
- kubectl (installed via winget).
Keep everything running
No action needed. Choose this if you're still actively experimenting with Kubernetes commands or want to try more deployments.
- Your Minikube cluster will continue running in the background using ~2 GB of RAM.
- Minikube and kubectl will remain installed and available in PowerShell.
- Your YAML files stay on disk for future reference.
Pause - I'll come back to this later
Stop the cluster without deleting it. This frees up RAM while preserving your cluster state so you can resume later.
- Stop the Minikube cluster by running:
minikube stop
This shuts down the cluster container but keeps all your Deployments, Services, and configuration intact.
- When you're ready to resume, start the cluster again by running:
minikube start
Your pods and services will come back up automatically.
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees all disk space and RAM used by the cluster.
- Delete the Minikube cluster entirely by running:
minikube delete
This removes the cluster container, all Kubernetes resources, and downloaded images.
- Delete the local project files by running:
del deployment.yaml
del hpa.yaml
del service.yaml
- Uninstall Minikube by running:
winget uninstall --id Kubernetes.minikube
- Uninstall kubectl by running:
winget uninstall --id Kubernetes.kubectl
You can recreate the entire setup anytime by following this project again from Step 1.
Nice Work!
Nice Work!
You just deployed and managed your first application on Kubernetes. These are the exact same operations DevOps engineers perform on production clusters daily.
You've learned how to:
- Deployed a multi-replica application on a local Kubernetes cluster using Minikube and Docker, writing Deployment and Service manifests from scratch.
- Managed your application with Kubernetes superpowers: self-healing (deleted a pod and watched it recreate), scaling (up and down with a single command), and rolling updates and rollbacks with zero downtime.
- Exposed your app to the browser with a NodePort Service and observed load balancing across pods in real time.
- Configured a Horizontal Pod Autoscaler for automatic CPU-based scaling, watching Kubernetes add and remove pods based on live traffic.
Ready to quiz yourself?