Deploy and Scale Your First App on K8s
Deploy, scale, and update an nginx app on a local Kubernetes cluster.
Introduction
30 Second Summary
Every time you use a popular app or website, multiple copies of that application are running behind the scenes. If one copy crashes, another takes over so seamlessly you never even notice.
In this project, you will set up a local Kubernetes cluster on your Mac and deploy an nginx web server. You'll scale your app to multiple replicas and perform a rolling update with zero downtime.
What You'll Build
A locally running Kubernetes environment where you deploy, scale, and update an nginx web server using hand-written YAML manifests and kubectl commands.
By the end of this project, you'll have:
- A running Minikube cluster on your Mac with nginx deployed as your first app.
- Hand-written Deployment and Service manifests that create and manage nginx Pods.
- Experience scaling replicas and performing a rolling update with zero downtime.
- Secret Mission: Add readiness probes and resource limits to make your Deployment production-ready.
Are there any prerequisites?
This project includes full setup instructions for every tool. You just need a Mac with at least 2 CPUs, 2GB of free RAM, and 20GB of free disk space.
Install Your Kubernetes Toolchain
Kubernetes runs inside a cluster of nodes. To run it locally on your Mac, you need a few tools that create and manage this cluster for you.
This step walks you through installing Homebrew (the macOS package manager), Docker Desktop (the container engine), Minikube (the local cluster tool), and kubectl (the Kubernetes command-line interface).
In this step, get ready to:
- Install Homebrew and Docker Desktop.
- Install kubectl and Minikube.
- Start your local Minikube cluster.
Install Homebrew and Docker Desktop
- Press Cmd+Space to open Spotlight.
- Type Terminal and press Enter to open it.
- Install Homebrew by running this command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
What to expect during installation
The installer asks for your Mac password. You won't see any characters appear on screen as you type. Just type your password and press Enter.
It may also install Xcode Command Line Tools, which can add a few extra minutes. On Apple Silicon Macs, the installer prints instructions at the end to add Homebrew to your PATH. Run those commands before continuing.
- Confirm Homebrew installed successfully by running:
brew --version
You should see a version number in the output.
Seeing command not found?
If your terminal does not recognize brew, Homebrew is not in your PATH. Follow the PATH instructions the Homebrew installer printed at the end of installation. Then open a new Terminal window and try again.
Help me fix Homebrew not being recognized in my Terminal.
- Install Docker Desktop by running:
brew install --cask docker
- Press Cmd+Space to open Spotlight.
- Type Docker and press Enter to launch Docker Desktop.
- If Docker Desktop asks for permissions, click OK to allow them.
- Look for the whale icon in your Mac's top menu bar. Hover over it to confirm it says Docker Desktop is running.
Why does Minikube need Docker Desktop?
Minikube creates your Kubernetes cluster by running it inside a Docker container. Docker Desktop provides the container engine that makes this possible. The Docker engine must be running any time you want to use your Minikube cluster.
Docker engine not starting?
Make sure you allowed all the permissions Docker Desktop requested. You can check these in System Settings on your Mac.
If the engine still won't start, try quitting Docker Desktop completely and reopening it.
Help me get Docker Desktop running on my Mac.
Install kubectl and Minikube
- Install kubectl and Minikube by running these commands one at a time:
brew install kubectl
brew install minikube
- Verify both tools installed by running these commands one at a time:
kubectl version --client
minikube version
Each command should print a version number.
Seeing command not found?
This usually means Homebrew's bin directory is not in your PATH. Follow the PATH instructions from the Homebrew installation, then open a new Terminal window and try again.
Help me fix kubectl or minikube not being found after installing with Homebrew.
Start your Minikube cluster
Minikube creates a single-node Kubernetes cluster running inside a Docker container on your Mac. Starting it also configures kubectl to communicate with your new cluster automatically.
- Start your local Kubernetes cluster by running:
minikube start
What is happening?
Minikube downloads a Kubernetes node image and starts it inside Docker. This can take a minute or two on the first run.
Once it finishes, kubectl is automatically configured to point at your new cluster. No extra setup needed.
- Verify your cluster is running by running:
kubectl get nodes
You should see one node named minikube with a status of Ready.
Cluster not starting?
Make sure Docker Desktop is running first. Minikube uses Docker as its driver, so the Docker engine must be active before you run minikube start.
If you see a timeout error, try running minikube delete followed by minikube start to start fresh.
Help me troubleshoot my Minikube cluster not starting.
Your Kubernetes toolchain is ready to go. Next up, you'll write your first Deployment manifest and get an nginx web server running on your cluster.
Deploy Your First Pod with a Deployment
Your Kubernetes cluster is up and running with one node ready to accept work. Now it's time to put something on it.
In this step, you'll write a Deployment manifest that tells Kubernetes to run an nginx web server. Deployments manage the lifecycle of your Pods, so if one crashes, Kubernetes automatically replaces it.
In this step, get ready to:
- Create an nginx-deployment.yaml manifest that defines your nginx web server.
- Apply the manifest to your cluster and inspect the running Pod.
Create the Deployment manifest
Kubernetes uses YAML files called manifests to describe the desired state of your cluster. You're going to write one that defines a Deployment with a single nginx container.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder, move into it, and create your manifest file by running these commands:
mkdir k8s-nginx
cd k8s-nginx
touch nginx-deployment.yaml
- Confirm the file was created by running:
ls
You should see nginx-deployment.yaml in the output.
Don't see the file?
Make sure you ran mkdir k8s-nginx and cd k8s-nginx before running touch. If you skipped the cd, the file was created in your Desktop folder instead.
Help me find my nginx-deployment.yaml file
- Open nginx-deployment.yaml in a text editor and paste the following Deployment manifest:
# Tell Kubernetes this is a Deployment resource
apiVersion: apps/v1
kind: Deployment
metadata:
# Name your Deployment so you can reference it later
name: nginx-deployment
labels:
app: nginx
spec:
# Run one copy (replica) of your Pod
replicas: 1
selector:
# Find Pods with this label
matchLabels:
app: nginx
template:
metadata:
# Label every Pod this Deployment creates
labels:
app: nginx
spec:
containers:
# Define the container that runs inside each Pod
- name: nginx
image: nginx:1.26
ports:
- containerPort: 80
What does this manifest do?
- apiVersion: apps/v1 and kind: Deployment tell Kubernetes you want to create a Deployment resource.
- replicas: 1 means Kubernetes runs exactly one copy of your Pod.
- selector.matchLabels tells the Deployment which Pods belong to it by matching the label app: nginx. This MUST match the labels in template.metadata.labels.
- The template section is the blueprint for each Pod. It defines one container named nginx running the nginx:1.26 image on port 80.
Why do the labels have to match?
Labels are key-value tags you attach to Kubernetes resources. A Deployment uses its selector to find the Pods it manages by matching these labels.
If selector.matchLabels and template.metadata.labels don't match, the Deployment can't find its own Pods. Kubernetes will reject the manifest with a validation error.
- Save nginx-deployment.yaml.
Seeing a YAML syntax error later?
YAML is sensitive to indentation. Every level of nesting uses exactly two spaces. Tabs will cause errors.
Check that matchLabels is indented under selector, and that containers is indented under spec inside the template block.
Help me fix a YAML indentation error in my Deployment manifest
✔️ 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. Make sure your file matches this exactly.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.26
ports:
- containerPort: 80
Apply the manifest and inspect your Pod
Your manifest describes what you want. Now you'll hand it to Kubernetes and let the cluster make it real.
- Apply your Deployment to the cluster by running:
kubectl apply -f nginx-deployment.yaml
You should see deployment.apps/nginx-deployment created in your terminal.
What just happened?
Kubernetes read your manifest and created a Deployment. The Deployment then created a ReplicaSet, which in turn created one Pod running your nginx container.
You only wrote the Deployment manifest, but Kubernetes handled the chain of resources underneath it automatically.
Seeing an error instead?
If you see error: the path "nginx-deployment.yaml" does not exist, make sure you are inside the k8s-nginx folder where you created the file. Run ls to check.
If you see a YAML parsing error, check your indentation. Every nested level uses exactly two spaces, not tabs.
Help me fix my kubectl apply error
- Check your Deployment status by running:
kubectl get deployments
You should see nginx-deployment with 1/1 in the READY column.
- List the Pods your Deployment created by running:
kubectl get pods
You should see one Pod with a name starting with nginx-deployment- and a STATUS of Running.
- Copy the full Pod name from the NAME column in the output above.
- Inspect your Pod's details by running:
kubectl describe pod [[POD_NAME="your Pod name from kubectl get pods"]]
What does describe show?
The describe command gives you a detailed view of the Pod. Look for these key sections:
- Containers shows the container image (nginx:1.26) and its current state.
- Node tells you which node in the cluster is running this Pod.
- Events at the bottom shows a timeline of what happened. You should see events like Scheduled, Pulled, and Started.
Pod not showing as Running?
If the STATUS column shows ContainerCreating, wait a few seconds and run kubectl get pods again. Kubernetes is pulling the nginx image from Docker Hub.
If the STATUS shows ErrImagePull or ImagePullBackOff, check that Docker Desktop is still running and that you have an internet connection.
Help me troubleshoot my Pod that won't start
Your nginx Pod is running inside the cluster. Next up, you'll create a Service to expose it so you can see the nginx welcome page in your browser.
Expose Your App with a Service
In the last step, you deployed your first nginx Pod using a Deployment manifest. Your app is running inside the cluster, but it has no stable address.
If the Pod crashes and gets replaced, the new Pod receives a completely different IP address. A Service solves this by creating a permanent endpoint that always routes traffic to the right Pods.
In this step, get ready to:
- Create a Service manifest that provides stable networking for your nginx Pods.
- Apply the Service to your cluster and verify it is running.
- Access the nginx welcome page in your browser through the Service.
Create the Service manifest
Your Deployment's Pod template includes the label app: nginx. The Service you are about to create uses this same label to find and route traffic to the right Pods.
- Create a new file called nginx-service.yaml in the same folder as nginx-deployment.yaml, then paste the following code:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
# Expose the Service outside the cluster
type: NodePort
# Route traffic to Pods with this label
selector:
app: nginx
ports:
- port: 80 # Port the Service listens on inside the cluster
targetPort: 80 # Port on the container to forward traffic to
What does this manifest do?
- type: NodePort exposes the Service on a port on your Minikube node. This lets you reach it from your browser.
- selector: app: nginx tells the Service to route traffic to any Pod that carries the label app: nginx. This matches the labels in your Deployment's Pod template.
- port: 80 is the port the Service listens on inside the cluster.
- targetPort: 80 is the port on the container where traffic gets forwarded. This must match the containerPort in your Deployment.
- Save nginx-service.yaml.
- You won't see a change yet. The next substep applies this manifest to your cluster.
Worried about YAML formatting?
- YAML is sensitive to indentation. Make sure you use spaces, not tabs, and that each nested level is indented by exactly 2 spaces.
- Double-check that selector and ports are at the same indentation level under spec.
Help me check the formatting of my nginx-service.yaml file.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete nginx-service.yaml file for comparison.
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Apply the Service and access your app
- Apply your Service manifest to the cluster by running this command:
kubectl apply -f nginx-service.yaml
- Verify the Service is running by running:
kubectl get services
What should I see?
You should see nginx-service listed with type NodePort and a port value like 80:3XXXX/TCP. The second number is the NodePort that Kubernetes automatically assigned.
Seeing an error after applying?
- If you see error: the path "nginx-service.yaml" does not exist, make sure your terminal is in the same directory where you saved the file.
- If you see a YAML parsing error, double-check your indentation. YAML requires spaces, not tabs.
Help me fix a kubectl apply error.
- Open the nginx welcome page in your browser by running:
minikube service nginx-service
Your browser opens and displays the nginx welcome page. This confirms that traffic is flowing from your browser, through the Service, and into the Pod.
Browser not opening?
Make sure Docker Desktop is still running. Minikube needs Docker to route traffic to your cluster.
If the browser still does not open, get the URL directly by running:
minikube service nginx-service --url
Copy the URL from the terminal output and paste it into your browser. Why can't I access my Kubernetes Service in the browser?
Why does a Service matter?
Even if the Pod behind the Service crashes and gets replaced, the Service keeps routing traffic to the new Pod. You never have to update an IP address.
Your app now has a stable network endpoint. Next up, you will scale your Deployment from one replica to three and test how Kubernetes handles Pod failures.
Scale Your Deployment
Your nginx app is live and accessible through a Service. Right now though, it's running on just a single Pod.
If that Pod crashes, your app goes down until Kubernetes replaces it. In this step, you'll scale your Deployment from 1 to 3 replicas and test what happens when a Pod dies.
In this step, get ready to:
- Scale your nginx Deployment to 3 replicas.
- Test self-healing by deleting a Pod and watching Kubernetes replace it.
Scale up and observe
One replica means one Pod. If that Pod fails, your app is unreachable until Kubernetes spins up a replacement. Running multiple replicas means traffic gets distributed across Pods, and your app survives individual failures.
- Scale your Deployment to 3 replicas by running this command:
kubectl scale deployment nginx-deployment --replicas=3
- Verify your new Pods are coming online by running:
kubectl get pods
You should see three Pods listed, all with names starting with nginx-deployment. Some may still show a status of ContainerCreating for a few seconds before switching to Running.
Watch Pods in real time
Add the -w flag to watch for changes in real time: kubectl get pods -w. Press Ctrl+C to stop watching when all three Pods show Running.
- Confirm all 3 replicas are ready by running:
kubectl get deployment nginx-deployment
You should see 3/3 under the READY column. This means all three Pods are up and accepting traffic.
Not seeing 3 Pods?
Make sure you typed the Deployment name exactly as nginx-deployment. A typo in the name will give you a "not found" error.
If your Pods are stuck in Pending status, Docker Desktop may be low on resources. Open Docker Desktop and check that it has at least 2 CPUs and 2 GB of memory allocated.
Still stuck? Help me figure out why my Pods are not coming online after scaling.
Test resilience by deleting a Pod
Having 3 replicas only helps if Kubernetes actually replaces a Pod when it goes down. Let's prove that by deleting a Pod on purpose and watching what happens.
- List your running Pods to grab a Pod name by running:
kubectl get pods
- Copy the full name of any one of the three Pods. It will look something like nginx-deployment-xxxxx-xxxxx.
- Delete that Pod to simulate a crash by running:
kubectl delete pod [[POD_NAME="one of your Pod names from above"]]
- Immediately check your Pods again by running:
kubectl get pods
You should see a brand new Pod spinning up (or already running) to replace the one you deleted. The total count is back to 3.
What just happened?
This is the Deployment controller in action. It constantly compares the actual state (how many Pods are running) with the desired state (replicas: 3).
The moment the count drops below 3, the controller creates a new Pod to bring it back. You never had to intervene. This is what makes Kubernetes so powerful for running production workloads.
Still seeing only 2 Pods?
Wait a few seconds and run kubectl get pods again. The replacement Pod can take a moment to start.
If the new Pod is stuck in Pending or CrashLoopBackOff, run kubectl describe pod <pod-name> and check the Events section at the bottom for clues.
Need a hand? Help me troubleshoot why Kubernetes is not replacing my deleted Pod.
Your Deployment is running three resilient replicas. Next up, you'll perform a rolling update to upgrade your app to a new version with zero downtime.
Perform a Rolling Update
Your Deployment is running 3 replicas. Kubernetes proved it can heal itself by replacing a crashed Pod automatically.
Deleting all your Pods to deploy a new version would cause downtime. Kubernetes rolling updates solve this by gradually replacing old Pods with new ones, so your app stays available the entire time.
In this step, get ready to:
- Update your nginx container image to a newer version using a rolling update.
- Verify the update completed and explore rollout history.
- Roll back to the previous version with zero downtime.
Update the container image
Right now, all 3 of your Pods are running nginx:1.26. You are going to update them to nginx:1.27 without any downtime.
- Update the nginx image on your Deployment by running this command:
kubectl set image deployment/nginx-deployment nginx=nginx:1.27
What does this command do?
kubectl set image tells Kubernetes to change the container image for a specific Deployment.
The nginx=nginx:1.27 part means: find the container named nginx in the Pod template and swap its image to nginx:1.27. Kubernetes then starts replacing old Pods one at a time, creating new Pods with the updated image before terminating old ones.
- Watch the rolling update progress by running:
kubectl rollout status deployment/nginx-deployment
You should see messages like Waiting for deployment "nginx-deployment" rollout to finish followed by deployment "nginx-deployment" successfully rolled out.
What just happened behind the scenes?
Kubernetes created new Pods running nginx:1.27 and waited for each one to become ready. Then it terminated the old nginx:1.26 Pods one at a time. At no point were all Pods down simultaneously.
Rollout stuck or seeing an error?
- Make sure Docker Desktop is still running. Minikube needs it to pull the new image.
- Check that you typed the image name exactly as nginx:1.27. A typo in the image name causes Pods to fail with an ImagePullBackOff error.
- Run kubectl get pods to check if any Pods are stuck in ErrImagePull or ImagePullBackOff status.
Help me troubleshoot my rolling update
Verify the update and learn rollback
Knowing how to roll back is just as important as knowing how to update. If a new version introduces a bug, you need to revert quickly.
- Confirm the Deployment is running the updated image by running:
kubectl describe deployment nginx-deployment
Look for the Image field in the output. It should now say nginx:1.27.
- View the revision history of your Deployment by running:
kubectl rollout history deployment/nginx-deployment
What is rollout history?
Kubernetes keeps a record of every change you make to a Deployment. Each change is stored as a revision. You should see at least two revisions: revision 1 (the original nginx:1.26) and revision 2 (the updated nginx:1.27).
This history is what makes rollback possible. Kubernetes can revert to any previous revision instantly.
- Roll back to the previous version by running:
kubectl rollout undo deployment/nginx-deployment
What does rollout undo do?
kubectl rollout undo reverts your Deployment to the previous revision. Kubernetes performs another rolling update, this time replacing the nginx:1.27 Pods with nginx:1.26 Pods.
- Verify the rollback completed by running:
kubectl describe deployment nginx-deployment
The Image field should now show nginx:1.26 again. Your Deployment is back to its original version.
Image still showing nginx:1.27?
- The rollback performs its own rolling update, so it takes a few seconds. Run kubectl rollout status deployment/nginx-deployment and wait for it to complete before checking again.
- Make sure you ran kubectl rollout undo deployment/nginx-deployment and not just kubectl rollout history. The history command only displays revisions. The undo command actually reverts.
Help me troubleshoot my Kubernetes rollback
Secret mission
Make Your Deployment Production-Ready
Your Deployment works, but would it survive a real production environment? Add readiness probes and resource limits to make your deployment truly production-ready.
Before you go
Clean Up Your Resources
This project runs entirely on your local machine, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Homebrew (macOS package manager).
- Docker Desktop (container runtime).
- Minikube (local Kubernetes cluster).
- kubectl (Kubernetes CLI).
- nginx-deployment.yaml manifest file.
- nginx-service.yaml manifest file.
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment more with your cluster.
- Your Minikube cluster stays available whenever Docker Desktop is running.
- If you restart your Mac, just launch Docker Desktop and run minikube start to pick up where you left off.
- Your nginx-deployment.yaml and nginx-service.yaml files stay on your Desktop.
Pause - I'll come back to this later
Shut down the cluster to free up CPU and memory. Your cluster configuration and manifest files are preserved so you can resume later.
- Stop the Minikube cluster by running minikube stop in your terminal.
- When you are ready to resume, launch Docker Desktop and run minikube start to bring the cluster back up.
- Your nginx-deployment.yaml and nginx-service.yaml files remain on your Desktop.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your cluster, its data, and the Kubernetes resources inside it.
- Delete the Kubernetes resources first by running kubectl delete -f nginx-deployment.yaml and kubectl delete -f nginx-service.yaml in your terminal.
- Delete the Minikube cluster by running minikube delete --all.
- Delete the nginx-deployment.yaml and nginx-service.yaml files from your Desktop.
- Optionally, uninstall Minikube and kubectl by running brew uninstall minikube and brew uninstall kubectl.
- Optionally, quit and uninstall Docker Desktop if you no longer need it for other projects.
Mission accomplished
That's a Wrap!
Nice work! You just deployed, scaled, and updated your first Kubernetes application from scratch on your own machine.
Here's what you learned:
- Set up a complete local Kubernetes environment with Minikube, Docker Desktop, and kubectl.
- Created Deployment and Service YAML manifests from scratch to deploy, manage, and expose nginx Pods.
- Scaled your Deployment to 3 replicas, tested self-healing by deleting a Pod, and performed a rolling update with zero downtime.
- Secret Mission: Added a readiness probe and resource limits to make your Deployment production-ready.
Ready to quiz yourself?