Deploy Your First App on Kubernetes

Deploy a scaled Nginx web server to a local Kubernetes cluster.

Introduction

30 Second Summary

Every app you use runs on a server somewhere. When companies need to run hundreds of copies of their apps across many servers, they use Kubernetes to manage it all. It is the most in-demand skill in cloud computing today.

In this project, you will set up a local Kubernetes cluster on your Mac and deploy a real web server to it. You will write configuration files from scratch, scale your app up and down, and see it running live in your browser.

What You'll Build

You will deploy an Nginx web server to a local Kubernetes cluster using declarative YAML manifests and kubectl.

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

  • A running Minikube Kubernetes cluster on your Mac.
  • A Deployment manifest that runs 3 replicas of Nginx and a Service manifest that exposes your app to the browser.
  • Hands-on experience scaling your application up and down and exploring your cluster with the Minikube dashboard.
  • Secret Mission: Perform a rolling update to deploy a new version of your app with zero downtime.

Are there any prerequisites?

You need a Mac with Docker Desktop and Homebrew already installed. You should also be comfortable with basic terminal tasks like navigating directories and running commands.

Before We Start

Before We Start...

Install Minikube and kubectl

Before you can deploy anything to a Kubernetes cluster, you need two command-line tools installed on your Mac.

In this step, you'll use Homebrew to install kubectl and Minikube. Together they give you everything you need to create and manage a local Kubernetes cluster.

In this step, get ready to:
  • Install kubectl, the Kubernetes command-line tool.
  • Install Minikube to run a local Kubernetes cluster.
  • Start your cluster and verify it's running.
Install kubectl

kubectl is the command-line tool you'll use to communicate with your Kubernetes cluster. Every command you run in this project goes through it.

  • Press Cmd+Space to open Spotlight Search.
  • Type Terminal and press Enter to open it.
  • Install kubectl using Homebrew by running this command:
brew install kubectl
  • Verify the installation by running:
kubectl version --client

You should see a client version number in the output. The exact version doesn't matter as long as the command runs without error.

Command not found?

Make sure the brew install kubectl command finished without errors. Look for red error text in your terminal output.

Try closing and reopening Terminal, then run kubectl version --client again. A fresh terminal session picks up newly installed commands.

Why isn't kubectl working after I installed it with Homebrew?

Install Minikube

Minikube creates a single-node Kubernetes cluster right on your laptop. It's the fastest way to get a real cluster running locally for learning and development.

  • Install Minikube using Homebrew by running this command:
brew install minikube
  • Verify the installation by running:
minikube version

You should see a version number in the output, such as v1.38.1. The exact version may differ depending on when you install.

Command not found?

Double check that the brew install minikube command completed without errors. Scroll up in your terminal to look for any red error messages.

Try closing and reopening your Terminal, then run minikube version again.

Why isn't minikube working after I installed it with Homebrew?

Start your Minikube cluster

Minikube uses Docker Desktop as its container engine to create your cluster. Docker Desktop needs to be running in the background before you can start Minikube.

  • Make sure Docker Desktop is running on your Mac.
  • If it's not open, press Cmd+Space, type Docker, and press Enter to launch it.
  • Wait until the Docker Desktop icon in your menu bar shows a steady state (no spinning animation).
  • Start your Minikube cluster by running this command:
minikube start

What does this command do?

minikube start downloads a small virtual machine image, creates a single-node Kubernetes cluster inside it, and configures kubectl to connect to that cluster automatically.

This may take a minute or two the first time it runs. You'll see progress messages in your terminal as Minikube sets everything up.

  • Verify your cluster is running by running:
kubectl get nodes

You should see one node listed with a status of Ready. This confirms your cluster is up and kubectl can communicate with it.

Node not showing as Ready?

Make sure Docker Desktop is fully running. Minikube cannot create a cluster without Docker's container engine.

If minikube start failed with an error, try deleting the broken cluster and starting fresh by running:

minikube delete
minikube start

Why is my Minikube cluster not starting?

Your Kubernetes environment is ready. Next up, you'll write your first Deployment manifest and deploy a real web server to your cluster.

Write and Apply Your Deployment Manifest

Your Minikube cluster is up and running with one node in Ready state. Now it's time to deploy something to it.

In Kubernetes, you describe what you want to run using YAML files called manifests. Kubernetes reads your manifest and makes it happen. You'll write a manifest that tells Kubernetes to run three copies of an Nginx web server.

In this step, get ready to:
  • Create a Deployment manifest that runs 3 replicas of Nginx.
  • Apply the manifest to your Kubernetes cluster.
  • Verify your Deployment and Pods are running.
Create your Deployment manifest

A Deployment is a Kubernetes resource that manages a set of identical Pods. Each Pod runs one container. Your Deployment will tell Kubernetes to keep 3 Nginx Pods running at all times.

What is a Deployment?

A Deployment is like a manager for your app. You tell it what container image to run and how many copies you want. Kubernetes then creates that many Pods and keeps them healthy.

If a Pod crashes, the Deployment automatically replaces it. You never manage individual Pods yourself.

First, you need a project folder to keep your manifest files organized.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir kubernetes-nginx
cd kubernetes-nginx
  • Confirm you're inside the folder by running:
ls

The output should be empty since the folder is brand new.

  • Create your manifest file by running:
touch nginx-deployment.yaml
  • Confirm the file was created by running:
ls

You should see nginx-deployment.yaml in the output.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type the name of your text editor (e.g. VS Code, Sublime Text, TextEdit) and press Enter to open it.
  • Open the nginx-deployment.yaml file from the kubernetes-nginx folder on your Desktop.
  • Paste the following YAML into the file:
# Define the API version and resource type
apiVersion: apps/v1
kind: Deployment
metadata:
  # Name your Deployment so you can reference it later
  name: nginx-deployment
  labels:
    app: nginx
spec:
  # Run 3 identical copies (replicas) of the container
  replicas: 3
  selector:
    # Tell the Deployment which Pods to manage by matching this label
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      # Define the container to run inside each Pod
      - name: nginx
        image: nginx:stable-alpine
        ports:
        - containerPort: 80
        # Set resource boundaries so containers don't consume unlimited resources
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "250m"

What does this manifest do?

  • apiVersion: apps/v1 and kind: Deployment tell Kubernetes you're creating a Deployment resource.
  • replicas: 3 tells Kubernetes to run 3 identical Pods. If one crashes, Kubernetes automatically creates a replacement.
  • The selector and template sections connect the Deployment to its Pods using the label app: nginx.
  • image: nginx:stable-alpine specifies the container image each Pod runs. Alpine is a lightweight Linux variant that keeps the image small.
  • The resources section sets memory and CPU boundaries. requests is the minimum guaranteed. limits is the maximum allowed.
  • Save nginx-deployment.yaml.

You won't see a change yet. The manifest is just a file on your computer until you apply it to your cluster in the next substep.

✔️ 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 nginx-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:stable-alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "250m"

Worried about YAML formatting?

YAML is very sensitive to indentation. Every level of nesting uses exactly 2 spaces. Tabs will cause errors.

If you see strange errors later when applying the file, check that your indentation matches the example exactly. The spec, template, and containers sections each nest one level deeper.

Help me fix YAML indentation errors in my nginx-deployment.yaml file.

Apply the manifest to your cluster

Your manifest file describes what you want. The kubectl apply command sends that description to Kubernetes and tells it to make it real.

Why kubectl apply instead of kubectl create?

The kubectl apply command is declarative. It creates resources if they don't exist and updates them if they do. This is how production workflows manage Kubernetes because the same command works every time.

The kubectl create command is imperative. It only creates resources and fails if they already exist. You'll stick with apply throughout this project.

  • Make sure your terminal is in the kubernetes-nginx folder on your Desktop (where you saved the YAML file).
  • Apply your Deployment manifest by running:
kubectl apply -f nginx-deployment.yaml

You should see this output:

deployment.apps/nginx-deployment created

That confirms Kubernetes received your manifest and created the Deployment.

Seeing an error instead?

If you see error: the path "nginx-deployment.yaml" does not exist, your terminal is not in the same folder as the file. Run ls to check whether nginx-deployment.yaml is listed. If it's not, navigate to the kubernetes-nginx folder on your Desktop.

If you see a YAML parsing error, open your file and check the indentation. Each nested level must use exactly 2 spaces, not tabs.

Help me troubleshoot my kubectl apply error.

Verify your Deployment and Pods are running

Kubernetes accepted your manifest, but that doesn't mean everything is running yet. Pods take a moment to start, especially the first time when Kubernetes has to download the container image.

  • Check your Deployment status by running:
kubectl get deployments

You should see output like this:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           30s

What do these columns mean?

  • READY 3/3 means 3 out of 3 requested Pods are running and healthy.
  • UP-TO-DATE shows how many Pods are running the latest version of your manifest.
  • AVAILABLE shows how many Pods are ready to serve traffic.

If you see 0/3 or 1/3 in the READY column, wait about 30 seconds and run the command again. Kubernetes is still pulling the container image.

  • Check your individual Pods by running:
kubectl get pods

You should see 3 Pods, each with a status of Running.

NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-xxxxxxxxxx-xxxxx   1/1     Running   0          45s
nginx-deployment-xxxxxxxxxx-xxxxx   1/1     Running   0          45s
nginx-deployment-xxxxxxxxxx-xxxxx   1/1     Running   0          45s

Why do the Pod names look random?

Kubernetes automatically generates unique names for each Pod by appending random characters to the Deployment name. Your Pod names will be different from the example above. That's completely normal.

Pods stuck in Pending or ContainerCreating?

Make sure Docker Desktop is running. Minikube uses Docker as its container runtime, so Pods can't start without it.

If Pods stay in ContainerCreating for more than a minute, Kubernetes may still be downloading the Nginx image. Wait another 30 seconds and run kubectl get pods again.

Help me figure out why my Kubernetes Pods are stuck.

You now have 3 Nginx Pods running on your Kubernetes cluster. Next up, you'll create a Service that exposes these Pods so you can actually visit your web server in the browser.

Expose Your App with a Service

Your 3 Nginx pods are up and running inside the cluster. Right now, there's no way to reach them from your browser.

A Kubernetes Service creates a stable network endpoint that routes outside traffic to your pods. In this step, you'll write a Service manifest and use it to open your Nginx app in the browser.

In this step, get ready to:
  • Create a nginx-service.yaml manifest that defines a NodePort Service.
  • Apply the Service to your cluster.
  • Open your Nginx app in the browser.
Create the Service manifest

Your Deployment created 3 pods, but they're only reachable from inside the cluster. A Service gives them a stable network address that outside traffic can reach.

What is a Kubernetes Service?

Pods can restart at any time and get new IP addresses. A Service provides a fixed entry point that stays the same regardless of what happens to individual pods.

The Service uses label selectors to decide which pods should receive traffic. Any pod matching the label gets included automatically.

  • Create a new file called nginx-service.yaml in the same folder as nginx-deployment.yaml.
  • Open it in your text editor and paste the following code:
# API version and resource type
apiVersion: v1
kind: Service
metadata:
  # Name used to manage this Service with kubectl
  name: nginx-service
spec:
  # Route traffic to pods with this label
  selector:
    app: nginx
  ports:
  # Map incoming port 80 to the container's port 80
  - protocol: TCP
    port: 80
    targetPort: 80
  # Expose the Service outside the cluster
  type: NodePort

What does this manifest do?

  • selector: app: nginx tells the Service to route traffic to any pod with the label app: nginx. These are the 3 pods your Deployment created.
  • port: 80 is where the Service listens. targetPort: 80 is the port on the container where traffic gets forwarded.
  • type: NodePort exposes the Service on a port accessible from outside the cluster. This is what lets you reach your app from your browser.

Why NodePort instead of LoadBalancer?

In a local Minikube cluster, NodePort is the simplest way to access your app. Minikube has a built-in command (minikube service) that automatically finds the NodePort and opens it in your browser.

LoadBalancer is designed for cloud environments. Locally, it requires running minikube tunnel in a separate terminal window, which adds unnecessary complexity.

  • Save nginx-service.yaml.
  • Confirm the file exists by running:
ls

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

Don't see nginx-service.yaml?

  • Make sure you saved the file in the same folder where you ran the ls command.
  • Check that the file name is exactly nginx-service.yaml with no extra spaces or typos.
  • Help me find my nginx-service.yaml file.

✔️ Awesome, I've got everything!

Great. Double check you've saved nginx-service.yaml before moving on.

ⓧ I'd like to double check the full code

Here's the complete nginx-service.yaml file for comparison.

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: NodePort
Apply the Service to your cluster

Your manifest is ready. Now you'll tell Kubernetes to create the Service from it.

  • Apply the Service manifest by running:
kubectl apply -f nginx-service.yaml

You should see: service/nginx-service created.

  • Verify the Service is running by running:
kubectl get services

You should see nginx-service listed with a TYPE of NodePort and a PORT(S) column showing 80 mapped to a randomly assigned port number (something like 80:31234/TCP).

Seeing an error when applying?

  • Make sure you're in the same folder where nginx-service.yaml is saved. Run ls to confirm the file is there.
  • Check your YAML indentation. YAML is sensitive to spaces, and each nested level should be indented by exactly 2 spaces.
  • Help me fix my kubectl apply error for nginx-service.yaml.
Open your app in the browser

Minikube has a built-in command that finds your Service's URL and opens it directly in your browser. No manual URL hunting required.

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

Your default browser opens to the Nginx welcome page. You should see a heading that reads "Welcome to nginx!" along with some documentation links.

Browser didn't open?

  • Check the terminal output for a URL (it usually starts with http://). Copy it and paste it into your browser manually.
  • Make sure your Minikube cluster is still running. Run kubectl get nodes and confirm the node shows Ready.
  • Make sure Docker Desktop is still running. Minikube uses Docker as its driver, so the cluster stops working if Docker is closed.
  • Minikube service didn't open my browser. What should I do?

Your Nginx app is live in the browser. Next up, you'll learn how to scale your application up and down with a single command.

Scale Your Application

In the previous step, you created a Service and saw the Nginx welcome page live in your browser.

Now you'll experience one of Kubernetes' most powerful features: scaling. With a single command, you can increase or decrease the number of Pods running your app.

In this step, get ready to:
  • Scale your Deployment from 3 replicas to 5.
  • Watch new pods start up in real time.
  • Scale back down to 2 replicas and observe pods terminating.
Scale your Deployment to 5 replicas

Your Deployment is currently running 3 replicas of Nginx. Kubernetes lets you change that number on the fly without restarting or reconfiguring anything.

  • Tell Kubernetes you want 5 replicas by running this command:
kubectl scale deployment nginx-deployment --replicas=5

What does this command do?

  • kubectl scale tells Kubernetes to change the replica count for a resource.
  • deployment nginx-deployment targets your specific Deployment by name.
  • --replicas=5 sets the desired number of Pods to 5.
  • Verify that Kubernetes created the new Pods by running:
kubectl get pods

You should see 5 pods listed, all with the status Running.

Not seeing 5 pods?

  • Some pods might still show ContainerCreating if they haven't finished starting. Wait a few seconds and run kubectl get pods again.
  • Make sure Docker Desktop is still running. Minikube needs Docker to create new containers.

Help me troubleshoot why my pods aren't scaling to 5 replicas

Watch pod status changes in real time

The kubectl get pods command gives you a one-time snapshot. You can add a flag to stream status changes as they happen.

  • Start watching your pods in real time by running:
kubectl get pods -w

What does the -w flag do?

The -w flag stands for "watch." It keeps the command running and streams updates whenever a pod's status changes.

This is useful when you're scaling, deploying, or debugging. You'll see transitions like Pending to Running happen live in your terminal.

You should see all 5 pods listed with the status Running.

  • Press Ctrl+C to stop watching.
Scale back down to 2 replicas

Scaling up is only half the picture. Kubernetes can also remove Pods when you no longer need them.

  • Reduce your replica count to 2 by running:
kubectl scale deployment nginx-deployment --replicas=2
  • Watch the extra Pods get removed by running:
kubectl get pods -w

You should see 3 pods transition to Terminating and disappear, leaving only 2 pods with the status Running.

  • Press Ctrl+C to stop watching.

Why is scaling so fast?

Kubernetes doesn't reinstall or reconfigure anything when you scale. It simply starts or stops copies of the same container image that's already cached on your node.

This is the power of declarative configuration. You tell Kubernetes the desired state ("I want 2 replicas"), and it figures out how to get there on its own.

Pods stuck in Terminating?

  • Pods may take a few seconds to terminate gracefully. Wait 10-15 seconds and run kubectl get pods to confirm only 2 remain.
  • If pods are stuck in Terminating for more than a minute, Docker Desktop may be under heavy load. Give it another minute to catch up.

Help me figure out why my pods are stuck in Terminating status

You just scaled an application up and down with a single command. Next up, you'll dig deeper into your cluster and explore everything visually in the Minikube dashboard.

Explore Your Cluster

In the previous step, you scaled your app up and down. You saw how Kubernetes handles adding and removing Pods on the fly.

Managing a cluster also means understanding what's happening inside it. In this step, you'll use inspection commands and the Minikube dashboard to see the details behind your running Deployment, Pods, and Service.

In this step, get ready to:
  • Inspect your Deployment's details and events with kubectl describe.
  • View Nginx access logs from one of your running Pods.
  • Launch the Minikube dashboard to explore your cluster visually in the browser.
Inspect your Deployment details

Every Kubernetes resource stores detailed metadata, configuration, and a timeline of events. The kubectl describe command lets you see all of this in one view.

  • View detailed information about your Deployment by running this command:
kubectl describe deployment nginx-deployment

What should I see?

The output is long. Here are the key sections to look for:

  • Replicas shows 2 desired because you scaled down to 2 replicas in the previous step. Your YAML file still says replicas: 3, but the live cluster state was changed by your kubectl scale command.
  • Pod Template shows the container image (nginx:stable-alpine), port, and resource limits from your YAML.
  • Events at the bottom shows a timeline of actions. You should see scaling events from when you scaled up and down.
  • Scroll through the output and find the Events section near the bottom.
  • You should see entries for ScalingReplicaSet that match the scaling you did in Step 4.

Seeing 'not found'?

  • Make sure you typed the Deployment name exactly as nginx-deployment.
  • Run kubectl get deployments to confirm the Deployment exists in your cluster.
  • Why is kubectl describe not finding my nginx-deployment?
View your pod's Nginx logs

Every container running in your cluster writes logs to standard output. You can read these logs directly with kubectl logs.

  • List your running Pods to grab a pod name by running:
kubectl get pods

You should see 2 Pods listed, each with STATUS Running. Each pod has an auto-generated name like nginx-deployment-xxxxxxxxxx-xxxxx.

  • Copy one of the pod names from the NAME column.
  • View that pod's logs by running:
kubectl logs [[POD_NAME="your-pod-name-here"]]

What do the logs show?

Each line in the output is an Nginx access log entry. It represents an HTTP request that reached this Pod.

If you visited the Nginx welcome page earlier, those requests show up here. If the output is empty, the Pod hasn't received any traffic yet.

  • You should see at least one log line if you accessed the Nginx welcome page in Step 3.

Not seeing any logs?

  • Double check that you copied the full pod name from the NAME column of kubectl get pods.
  • If the logs are empty, open your Nginx page in the browser by running minikube service nginx-service. Then run the kubectl logs command again.
  • Why am I not seeing any Nginx logs from my pod?
Launch the Minikube dashboard

The terminal is powerful, but sometimes a visual overview is easier to navigate. Minikube includes a built-in web dashboard that shows your cluster's resources in the browser.

  • Open the Minikube dashboard by running:
minikube dashboard

A new browser tab opens with the Kubernetes dashboard. The command keeps running in your terminal. Leave it running while you explore.

What can I explore in the dashboard?

The dashboard gives you a visual overview of everything in your cluster. Here are a few things to check out:

  • Click Workloads in the left sidebar to see your Deployment and its Pods.
  • Click Services under the Service section to see your nginx-service.
  • Click on any Pod name to see its details, logs, and status.
  • When you're done exploring, switch back to your terminal and press Ctrl+C to stop the dashboard.

Dashboard not opening?

  • Make sure your Minikube cluster is still running. Run minikube status in a new terminal tab to check.
  • If the browser tab didn't open automatically, look in the terminal output for a URL starting with http://127.0.0.1: and paste it into your browser.
  • Why isn't the Minikube dashboard opening in my browser?

You've now explored your cluster from every angle. You can inspect any resource with kubectl describe, read container logs, and navigate the visual dashboard.

Secret mission

Rolling Update with Zero Downtime

Your app is live and running. But what happens when you need to deploy a new version? In this secret mission, you'll perform a rolling update that swaps your Nginx image without any downtime.

Before you go

Clean Up Your Resources

This project runs entirely on your Mac, so there are no ongoing cloud costs. However, your Minikube cluster does use memory and CPU while it is running. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Minikube cluster (local single-node Kubernetes cluster).
  • Nginx Deployment (3 Pods running inside the cluster).
  • Nginx Service (NodePort Service exposing your app).
  • YAML manifest files (nginx-deployment.yaml and nginx-service.yaml).
  • CLI tools installed via Homebrew (minikube and kubectl).

Keep everything running

No action needed. Choose this if you are still actively exploring your cluster or plan to continue experimenting right away.

  • Your Minikube cluster stays running in the background.
  • Your Deployment and Service remain active inside the cluster.
  • Your YAML files stay on your Desktop.

Pause - I'll come back to this later

Stop your Minikube cluster to free up memory and CPU. Your cluster configuration and deployed resources are saved, so you can resume later.

  • Stop your Minikube cluster by running:
minikube stop
  • When you are ready to come back, start it again with minikube start. Your Deployment and Service will still be there.
  • Keep your nginx-deployment.yaml and nginx-service.yaml files on your Desktop so you can reference them later.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your local cluster and everything deployed inside it.

  • Delete your Minikube cluster by running:
minikube delete
  • This removes the virtual machine, all deployed Pods, Services, and cluster data.
  • Delete your YAML manifest files (nginx-deployment.yaml and nginx-service.yaml) from your Desktop.
  • Optionally, uninstall the CLI tools by running:
brew uninstall minikube
brew uninstall kubectl
  • Only uninstall these tools if you do not plan to use Kubernetes again. They are lightweight and easy to reinstall later.

Mission accomplished

That's a Wrap!

Nice work! You just deployed, exposed, scaled, and explored a real application on Kubernetes. You went from zero to a running web server on a local cluster, all from your Mac.

Here's what you learned:

  • Set up a local Kubernetes cluster with Minikube and deployed Nginx using declarative YAML manifests you wrote from scratch.
  • Exposed your app to the browser with a Service and scaled it up and down with a single kubectl command.
  • Explored your cluster using kubectl commands and the Minikube dashboard to inspect Pods, Deployments, and Services.
  • Secret Mission: Performed a zero-downtime rolling update and learned how to roll back to a previous version.

Ready to quiz yourself?