Deploy Your First App on Kubernetes

Deploy, scale, and self-heal a containerized app on a local Kubernetes cluster.

Introduction

30 Second Summary

Every app you use daily stays online because something is watching over it, ready to restart it the moment it crashes. That something is usually Kubernetes.

In this project, you will deploy a web application to a local Kubernetes cluster, then test its self-healing and scaling powers by deliberately breaking things and watching them recover.

What You'll Build

A fully functioning Kubernetes deployment running on your laptop, complete with multiple replicas of a web server, browser access through a Service, and hands-on proof that Kubernetes recovers from failure automatically.

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

  • A local Kubernetes cluster running via Minikube.
  • A Deployment manifest managing 3 replicas of an nginx web server, exposed to your browser through a Service.
  • First-hand proof that Kubernetes self-heals and scales on demand.
  • Secret Mission: Perform a zero-downtime rolling update to a new version of your app.

Are there any prerequisites?

You just need basic familiarity with the command line. If you can open Terminal and run a command, you have everything you need to get started.

Install Your Tools

To deploy an application on Kubernetes, you need three tools working together on your machine.

In this step, you'll install Docker Desktop (the container engine), Minikube (a local Kubernetes cluster), and kubectl (the command-line tool for talking to Kubernetes).

In this step, get ready to:
  • Install Docker Desktop and confirm it's running.
  • Install Minikube via Homebrew.
  • Install kubectl via Homebrew.
Download and install Docker Desktop

Docker Desktop provides the container runtime that Minikube uses to run your local Kubernetes cluster.

  • Open your browser and navigate to docker.com/products/docker-desktop.
  • Click the download button for your Mac (choose Apple Silicon or Intel depending on your chip).
  • Double-click the downloaded Docker.dmg file to open the installer.
  • Drag the Docker icon into the Applications folder.

Which chip do I have?

Click the Apple menu in the top-left corner of your screen, then click About This Mac. If you see "Apple M1" (or M2, M3, M4), download the Apple Silicon version. If you see "Intel", download the Intel version.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Docker and press Enter to open Docker Desktop.
  • Wait for the whale icon in your menu bar to show "Docker Desktop is running."
  • Docker Desktop will display a Subscription Service Agreement on first launch. Accept it to continue.

Is Docker Desktop free?

Docker Desktop is free for personal use, education, and small businesses. You do not need a paid subscription for this project.

Now open Terminal so you can verify the installation.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Verify Docker is working by running this command:
docker --version

You should see a version number in the output. The exact numbers will depend on when you downloaded Docker Desktop.

✔️ I see a version number

Docker Desktop is installed and running. You're ready to move on.

ⓧ Command not found

Docker Desktop may still be starting up. Wait for the whale icon in your menu bar to stop animating, then try the command again.

If the whale icon isn't visible, open Docker Desktop again from your Applications folder and wait for it to finish loading.

Install Minikube

Minikube creates a single-node Kubernetes cluster on your laptop. You'll install it using Homebrew, a package manager for macOS.

  • Install Minikube by running this command in Terminal:
brew install minikube

Homebrew will download and install Minikube. This may take a minute or two.

  • Confirm Minikube is installed by running:
minikube version

✔️ I see a version number

Minikube is installed and ready. You should see something like minikube version: v1.38.1 (your version may be newer).

ⓧ Command not found

This means Homebrew is not installed on your machine. Visit brew.sh and follow the installation instructions. After Homebrew is installed, run brew install minikube again.

Seeing permission errors?

If Homebrew asks for your password, enter your Mac login password. The cursor won't move as you type. Press Enter when done.

If you see an error about Xcode Command Line Tools, run xcode-select --install in Terminal, then try the install again.

Help me troubleshoot my Minikube installation.

Install kubectl

The kubectl tool is how you communicate with your Kubernetes cluster. Every command you run against Kubernetes goes through kubectl.

  • Install kubectl by running this command:
brew install kubectl
  • Confirm kubectl is installed by running:
kubectl version --client

✔️ I see version information

kubectl is installed and ready. You should see client version details in the output.

ⓧ Command not found

Make sure the previous brew install kubectl command finished without errors. If Homebrew itself is not installed, visit brew.sh first.

Seeing a connection error?

The kubectl version --client command may print a warning about not reaching the cluster. That's expected. You haven't started a cluster yet. As long as you see client version information, you're good.

Help me fix my kubectl installation.

Your environment is ready. Next up, you'll start your Kubernetes cluster and see it come to life.

Start Your Kubernetes Cluster

You have all three tools installed. Now it's time to spin up an actual Kubernetes cluster on your machine.

Minikube creates a single-node cluster inside a Docker container. This gives you a fully functional Kubernetes environment without needing cloud infrastructure or multiple machines.

In this step, get ready to:
  • Start a local Kubernetes cluster with Minikube.
  • Verify the cluster is healthy and running.
  • Explore the system pods that power Kubernetes behind the scenes.
Start Minikube with the Docker driver

Minikube supports multiple drivers (virtual machines, containers, etc.) to run your cluster. On macOS with Docker Desktop already installed, the Docker driver is the preferred option.

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

What does this command do?

The --driver=docker flag tells Minikube to run your cluster inside a Docker container instead of a full virtual machine.

This is faster and lighter on your system's resources. Minikube downloads the necessary Kubernetes images, configures networking, and starts the control plane components automatically.

This command takes 1-2 minutes to complete. You'll see progress messages as Minikube downloads images and starts components.

When it finishes, you should see a message confirming that kubectl is now configured to use the cluster.

Minikube start hanging or failing?

Make sure Docker Desktop is running. Look for the whale icon in your macOS menu bar showing "Docker Desktop is running."

If Docker Desktop is running but the command still hangs, try restarting Docker Desktop from its menu bar icon, then run the command again.

Why is minikube start failing with Docker Desktop running?

Verify the cluster is healthy

Your cluster is running, but let's confirm everything started correctly. Kubernetes organizes machines into nodes. Your Minikube cluster has a single node that acts as both the control plane and the worker.

  • Check that your node is ready by running:
kubectl get nodes

What should I see?

You should see one node named minikube with a status of Ready. This means the node is healthy and able to accept workloads.

  • View the control plane URL by running:
kubectl cluster-info

What does this tell me?

This displays the URL where the Kubernetes API server is running. The API server is the front door to your cluster. Every command you run with kubectl talks to this endpoint.

Seeing "The connection to the server" errors?

This usually means the cluster didn't start properly. Run minikube status to check if the cluster components are running.

If components show as stopped, run minikube delete followed by minikube start --driver=docker to start fresh.

Help me fix kubectl connection errors with my Minikube cluster

Explore what's running behind the scenes

Kubernetes itself is made up of several system components that run as pods. These are the pieces that schedule your workloads, manage networking, and keep everything running smoothly.

  • View all system pods across every namespace by running:
kubectl get pods --all-namespaces

What are these system pods?

You'll see pods in the kube-system namespace. These include the API server, scheduler, controller manager, and DNS service.

The scheduler decides which node to place new pods on. The controller manager watches for changes and makes sure the actual state matches your desired state. You don't manage these directly. They work behind the scenes to power everything you deploy next.

Some pods may show Pending briefly

It's normal for a pod or two to briefly show Pending or ContainerCreating status right after cluster startup. Wait a few seconds and run the command again. All pods should reach Running status within a minute.

Your cluster is alive and all the system components are running. Next up, you'll deploy your own application onto this cluster.

Deploy Your Application

Your cluster is running and healthy. Now it needs something to actually run.

In this step, you'll write a Deployment manifest that tells Kubernetes exactly what you want: three copies of an nginx web server, always running, always recoverable. You'll submit that manifest to the cluster and watch Kubernetes bring your desired state to life.

In this step, get ready to:
  • Create a Deployment manifest defining 3 replicas of nginx.
  • Apply the manifest to your cluster and confirm all replicas are running.
  • Inspect a pod to see its full lifecycle details.
Create a Deployment manifest

A YAML manifest is how you declare your desired state to Kubernetes. Instead of running commands one by one, you write down what you want and let Kubernetes figure out how to make it happen.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create a project folder and move into it by running these commands:
mkdir k8s-deploy
cd k8s-deploy
  • Create the deployment.yaml file by running:
touch deployment.yaml
  • Confirm the file exists by running:
ls

You should see deployment.yaml in the output.

  • Open deployment.yaml in your preferred text editor and paste the following manifest:
# Define a Deployment resource
apiVersion: apps/v1
kind: Deployment
metadata:
  # Name that identifies this Deployment in the cluster
  name: nginx-deployment
  labels:
    app: nginx
spec:
  # Run 3 identical copies of the container
  replicas: 3
  # Tell Kubernetes which pods belong to this Deployment
  selector:
    matchLabels:
      app: nginx
  # Blueprint for each pod
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      # The container to run inside each pod
      - name: nginx
        image: nginx:1.26
        ports:
        - containerPort: 80

What does this manifest do?

This file describes your desired state in four key sections:

  • apiVersion: apps/v1 tells Kubernetes which API group handles Deployments.
  • replicas: 3 means Kubernetes will always maintain exactly 3 running copies of your container.
  • selector and labels link the Deployment to the pods it manages. Any pod with the label app: nginx belongs to this Deployment.
  • image: nginx:1.26 specifies the exact container image each pod runs. Port 80 is the standard HTTP port nginx listens on.

Why declarative YAML instead of imperative commands?

You could create a Deployment with a single kubectl create command. But YAML manifests are version-controllable, repeatable, and self-documenting. They represent the approach every production Kubernetes team uses.

You write what you want. Kubernetes figures out the how.

  • Save deployment.yaml.

Not sure if you saved correctly?

Make sure the file is saved with the exact name deployment.yaml (not deployment.yml or Deployment.yaml). YAML is indentation-sensitive, so use spaces (not tabs) and keep the structure exactly as shown.

help me check if my deployment.yaml is formatted correctly

✔️ Awesome, I've got everything!

Great. Make sure you have saved deployment.yaml before moving on.

ⓧ I'd like to double check the full code

Here is the complete deployment.yaml file for comparison:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.26
        ports:
        - containerPort: 80
Apply the manifest to your cluster

Your manifest describes what you want. Now you'll submit it to Kubernetes and let the cluster make it real.

  • Apply the manifest to your cluster by running this command:
kubectl apply -f deployment.yaml

You should see output confirming the Deployment was created:

deployment.apps/nginx-deployment created

What does kubectl apply do?

The kubectl apply -f command sends your manifest to the Kubernetes API server. The cluster reads your desired state (3 replicas of nginx) and starts creating Pods to match it. If you change the manifest later and re-apply, Kubernetes will reconcile the difference.

  • Confirm all 3 replicas are available by running:
kubectl get deployments

You should see nginx-deployment with 3/3 under the READY column. This means all three replicas are running.

  • View the individual pods Kubernetes created by running:
kubectl get pods

You should see 3 pods listed, each with a STATUS of Running. Each pod name starts with nginx-deployment- followed by a unique identifier.

Pods not showing as Running?

If pods show ContainerCreating status, wait 30 seconds and run kubectl get pods again. Kubernetes needs time to pull the nginx image the first time.

If pods show ErrImagePull or ImagePullBackOff, check that Docker Desktop is running and your internet connection is active. The cluster needs to download the nginx:1.26 image from Docker Hub.

why are my Kubernetes pods not reaching Running status

Inspect a pod

Each pod has a detailed lifecycle story. You can read it to understand when it was scheduled, which node it landed on, and what container image it pulled.

  • Pick any pod name from your kubectl get pods output above.
  • Inspect that pod by running this command (replacing the pod name with yours):
kubectl describe pod [[POD_NAME="your pod name from kubectl get pods output"]]

You'll see a detailed output with sections for Node, Labels, Status, Containers, Conditions, and Events.

What should I look for in the output?

The output is long, but focus on these key sections:

  • Node shows which machine in your cluster is running this pod (your minikube node).
  • Containers shows the image, port, and current state (Running) of your nginx container.
  • Events at the bottom shows a timeline of what happened: the pod was scheduled, the image was pulled, and the container was started.

Command not found or pod not found?

Make sure you copied the full pod name exactly as it appears in your kubectl get pods output, including the random suffix (for example, nginx-deployment-xxxxx-xxxxx). Pod names are case-sensitive.

help me find and describe my pod in Kubernetes

Your application is deployed and all 3 replicas are healthy. Next up, you'll expose these pods to the outside world so you can access nginx in your browser.

Expose Your App with a Service

Your 3 nginx pods are running, but there's a problem. Pods are ephemeral. They get new IP addresses every time they restart, and there's no single address you can hit in a browser to reach them.

A Service solves this by giving your pods a stable network endpoint. It acts as a load balancer that routes traffic to healthy pods, regardless of their individual IP addresses.

In this step, get ready to:
  • Create a Service manifest that targets your nginx pods.
  • Apply the Service and open your app in the browser.
  • Confirm load balancing is distributing traffic across all replicas.
Create a Service manifest

Just like the Deployment, you define a Service in a YAML manifest that tells Kubernetes what you want. A NodePort Service exposes your app on a port that's accessible from outside the cluster.

  • Create a new file called service.yaml in the same folder as your deployment.yaml file.
  • Paste the following into service.yaml:
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP

What does this manifest do?

  • type: NodePort makes the Service reachable from outside the cluster on a specific port.
  • selector: app: nginx tells the Service to route traffic to any pod with the label app: nginx. This matches the label you set in deployment.yaml.
  • port: 80 is the port the Service listens on. targetPort: 80 is the port on the container it forwards traffic to.
  • Save service.yaml.
  • Confirm the file exists by running:
ls

You should see both deployment.yaml and service.yaml in the output.

Don't see service.yaml?

Make sure you saved the file in the same directory where you ran ls. If you're in a different folder, use cd to navigate to the folder containing deployment.yaml.

Help me find where my service.yaml file was saved

✔️ 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

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
Apply the Service and access your app
  • Apply the Service to your cluster by running this command:
kubectl apply -f service.yaml

What does this command do?

kubectl apply -f service.yaml submits your Service definition to Kubernetes. The cluster reads the manifest and creates a stable network endpoint that routes traffic to your nginx pods.

You should see output confirming the Service was created.

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

What does this command do?

Minikube creates a tunnel to your Service and opens the URL in your default browser. This is necessary because NodePort Services in Minikube aren't directly accessible on localhost the way they would be on a cloud provider.

Your browser should open and display the default nginx welcome page with the heading "Welcome to nginx!" on it.

Don't see the nginx page?

Make sure Docker Desktop is still running. The whale icon in your menu bar should show "Docker Desktop is running."

If the browser didn't open automatically, check your terminal output. It should display a URL. Copy and paste that URL into your browser manually.

Help me troubleshoot why my nginx page isn't loading

Confirm load balancing

Your Service is now routing traffic to your pods. But how does it know which pods to send traffic to? Kubernetes maintains a list of endpoints for each Service. These are the IP addresses of pods that match the Service's selector.

  • View the endpoints for your Service by running:
kubectl get endpoints nginx-service

What should I see?

The output shows the IP addresses of all 3 pods behind the Service. Each IP corresponds to one of your nginx pods. Kubernetes automatically registers pod IPs when they start and deregisters them when they stop.

This means if a pod dies and gets replaced (which you'll test in the next step), the Service automatically updates its endpoint list. Traffic never gets sent to a pod that isn't ready.

Seeing empty or fewer than 3 endpoints?

Run kubectl get pods to check that all 3 pods are in Running status. If any pod is in CrashLoopBackOff or Pending, its IP won't appear in the endpoints list.

Also verify that the selector in service.yaml says app: nginx. This must match the labels on your pods exactly.

Help me figure out why my endpoints list is incomplete

Your app is live and load-balanced across 3 replicas. Next up, you'll put Kubernetes to the test by killing pods and scaling your deployment up and down.

Test Self-Healing and Scaling

Your app is live and accessible in the browser. But what happens when something goes wrong?

In production, containers crash, servers fail, and traffic spikes hit without warning. Kubernetes is built to handle all of this automatically. In this step, you'll prove it by deliberately breaking things and watching Kubernetes fix them.

In this step, get ready to:
  • Kill a pod and watch Kubernetes replace it automatically.
  • Scale your deployment up to 5 replicas.
  • Scale back down to 2 replicas.
Kill a pod and watch it recover

Your Deployment declared a desired state of 3 replicas. Kubernetes constantly watches the actual state and corrects any drift. Let's break something on purpose and see what happens.

  • List your current pods by running this command:
kubectl get pods

You should see 3 pods with names starting with nginx-deployment, all showing Running status.

  • Copy one of the pod names from the NAME column.
  • Delete that pod by running this command:
kubectl delete pod [[PODNAME="your pod name from the output above"]]

You'll see a confirmation message: pod "<pod-name>" deleted.

What is happening behind the scenes?

The moment the pod disappears, the Kubernetes control plane detects that only 2 pods exist but your Deployment specifies 3. It immediately schedules a brand-new pod to close the gap.

This all happens in seconds, without any manual intervention. This is self-healing.

  • Immediately check your pods again by running:
kubectl get pods

You should see 3 pods again. One of them has a different name and a much shorter AGE value. That's the replacement Kubernetes just created.

Still seeing only 2 pods?

If you waited a few seconds and still see only 2 pods, run kubectl get pods one more time. The replacement pod may still be in ContainerCreating status, which means it is spinning up and will be Running within a few seconds.

If you see an error when deleting, double-check that you copied the full pod name exactly as shown in the output.

Help me debug why my pod isn't being replaced.

Scale your deployment up

Self-healing maintains your desired state. But what if you want to change that desired state? Scaling lets you increase or decrease the number of replicas on the fly.

  • Scale your deployment to 5 replicas by running this command:
kubectl scale deployment nginx-deployment --replicas=5

What does this command do?

kubectl scale updates the desired replica count on your Deployment. Kubernetes sees that only 3 pods exist but 5 are now desired, so it creates 2 more.

  • Watch the new pods appear in real time by running:
kubectl get pods -w

The -w flag watches for changes in real time. You'll see new pods transition from Pending to ContainerCreating to Running.

  • Press Ctrl+C to stop watching once all 5 pods show Running status.

Pods stuck in Pending?

If pods stay in Pending for more than 30 seconds, your Minikube node may not have enough resources allocated. Try restarting Docker Desktop to free up memory.

Help me figure out why my pods are stuck in Pending.

Scale back down

Scaling up is one command. Scaling down is just as simple. Kubernetes gracefully terminates the extra pods to match your new desired state.

  • Scale your deployment back down to 2 replicas by running this command:
kubectl scale deployment nginx-deployment --replicas=2
  • Confirm only 2 pods remain by running:
kubectl get pods

You should see only 2 pods listed, both in Running status. Kubernetes terminated 3 pods to match your new desired state of 2 replicas.

Still seeing more than 2 pods?

Pods in Terminating status are shutting down gracefully. Wait a few seconds and run kubectl get pods again. They'll disappear once the graceful shutdown period completes.

Help me understand why my pods are still showing after scaling down.

Secret mission

Perform a Rolling Update

Kubernetes can update your application to a new version without any downtime. While one pod updates, the others continue serving traffic. Can you perform a zero-downtime rolling update and practice a rollback?

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 on your machine via Docker, so there are no ongoing cloud costs.

Resources you used:

  • Minikube cluster (with your nginx Deployment and Service running inside).
  • Local manifest files: deployment.yaml and service.yaml.
  • Docker cached container images (nginx and minikube system images).

Keep everything running

No action needed. Choose this if you're still actively experimenting with Kubernetes or plan to continue soon.

  • Your Minikube cluster stays running in the background via Docker Desktop.
  • It uses some CPU and RAM while active. You can check by looking at Docker Desktop's resource usage.
  • Docker Desktop, minikube, and kubectl remain installed on your machine for future projects.

Pause - I'll come back to this later

Shut down the cluster to free up CPU and memory, but keep it saved so you can resume later.

  • Stop your Minikube cluster by running:
minikube stop

This shuts down the cluster without deleting it. Your Deployment, Service, and all cluster state are preserved.

  • When you're ready to resume, start it back up by running:
minikube start

Everything picks up right where you left off.

Delete - I don't want to use this again

Remove all project resources and start fresh. This permanently deletes your cluster and all its contents.

  • Delete your Minikube cluster entirely by running:
minikube delete

This removes the cluster, all pods, Deployments, and Services running inside it.

  • Remove the local manifest files by running:
rm deployment.yaml service.yaml
  • Reclaim disk space from cached Docker images by running:
docker system prune

Docker will ask you to confirm. Type y and press Enter to proceed. This removes stopped containers, unused networks, and dangling images.

That's a Wrap!

That's a Wrap!

Nice work! You just deployed, scaled, and healed a real application on Kubernetes. Welcome to the world of container orchestration.

You've learned how to:

  • Built and ran a local Kubernetes cluster with Minikube, then deployed a multi-replica nginx application using declarative YAML manifests.
  • Exposed your app to the browser with a Service and confirmed load balancing across all pod replicas.
  • Proved Kubernetes self-heals by killing a pod and watching it respawn, then scaled your deployment up and down with a single command.
  • Performed a zero-downtime rolling update to a new container image and practiced an instant rollback to the previous version.

Ready to quiz yourself?