Deploy Your First App with Kubernetes

Containerize a website with Docker and deploy it to a local Kubernetes cluster.

Introduction

30 Second Summary

When you use an app or visit a website, that app is running on a server somewhere. DevOps engineers use tools like Docker and Kubernetes to package apps into portable containers and deploy them reliably.

In this project, you will build a personal website, package it into a Docker container, and deploy it to a Kubernetes cluster running entirely on your Mac.

What You'll Build

By the end, you will open your browser to localhost and see your own website served by three replicas of your app, all running on a Kubernetes cluster on your Mac.

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

  • Your own website running inside a Docker container you built from scratch, visible in your browser.
  • That same container deployed to a local Kubernetes cluster and exposed through a Service, so you can access it at localhost.
  • First-hand proof that Kubernetes self-heals: scale to three replicas, deliberately delete a pod, and watch a replacement spin up in seconds.
  • An optional Secret Mission to push your skills further.

Are there any prerequisites?

No prerequisites needed. This project includes setup instructions for all required tools, including Docker Desktop and Kubernetes.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters for your DevOps goals.

Set Up Your Kubernetes Cluster

Before you can deploy anything to Kubernetes, you need a running cluster. A cluster is the environment where all your containers will live and run.

Docker Desktop includes a built-in single-node cluster. In this step, you'll turn it on and verify it's ready.

In this step, get ready to:
  • Enable Kubernetes inside Docker Desktop.
  • Verify your cluster is running from the terminal.
Enable Kubernetes in Docker Desktop

Docker Desktop ships with a Kubernetes cluster built right in. All you need to do is flip one switch in the settings.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Docker Desktop and press Enter to open it.
  • Click the gear icon in the top right corner to open Settings.
  • Select the Kubernetes tab in the left sidebar.
  • Check the Enable Kubernetes checkbox.
  • Click Apply.

Docker Desktop will download and configure the Kubernetes components. This usually takes about 30 to 60 seconds.

  • Look for a green light beside Kubernetes running in Settings. When you see it, your cluster is ready.

What is a Kubernetes cluster?

A cluster is a group of machines that work together to run your containers. In this case, your Mac is the only machine in the cluster, making it a single-node cluster.

Kubernetes manages the cluster for you. It decides where to run your containers, restarts them if they crash, and handles networking between them.

Not seeing the green indicator?

Make sure Docker Desktop's engine is fully started before enabling Kubernetes. You should see the Docker Desktop dashboard loading without any startup messages.

If Kubernetes seems stuck, try closing Docker Desktop completely and reopening it.

Help me troubleshoot Kubernetes not starting in Docker Desktop.

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

You should see a Docker version number printed in the terminal.

Now for the real test. Before you run the next command, what do you expect to see from your brand-new Kubernetes cluster?

  • Check that your Kubernetes cluster is running by running this command:
kubectl get nodes

You should see one node named docker-desktop with STATUS Ready. That single node IS your entire cluster, running right on your Mac.

Seeing NotReady or a connection error?

If the STATUS shows NotReady, Kubernetes is still starting up. Wait 30 seconds and run kubectl get nodes again.

If you see a connection refused error, make sure Docker Desktop is running and Kubernetes is enabled in Settings.

Help me fix my kubectl connection.

Your cluster is up and running. Next up, you'll build your first Docker image and see your website live in the browser.

Build Your First Docker Image

Your Kubernetes cluster is running and ready for action. Now it needs something to actually run.

Before Kubernetes can deploy your app, it needs a Docker image. A Docker image is a snapshot of everything your app needs to run: the code, the web server, and the configuration. In this step, you will build a simple website, package it into an image, and see it live in your browser.

In this step, get ready to:
  • Create a project folder and a personal website HTML file.
  • Write a Dockerfile to package your website into a Docker image.
  • Build and run the Docker image to see your site live in the browser.
Create your project folder and HTML file

Every project starts with a home for your files. You will create a folder on your Desktop, then build a simple website inside it.

  • Switch back to your terminal and move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir my-first-app
cd my-first-app
  • Confirm the folder was created by running:
pwd

You should see /Users/<your-username>/Desktop/my-first-app in the output.

  • Open this folder in VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the my-first-app folder, then click Open.

VS Code asking you to trust the folder?

VS Code may show a dialog asking if you trust the authors of this folder. Click Yes, I trust the authors to continue.

  • In the Explorer sidebar on the left, hover over the MY-FIRST-APP heading and click the New File icon (a page with a plus sign).
  • Type index.html and press Enter.
  • Paste the following code into your new index.html file. This is the first section, which sets up the page head and layout styles:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My First Kubernetes App</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            background-color: #1a1a2e;
            color: #e0e0e0;
        }
        .container {
            text-align: center;
            padding: 40px;
            background-color: #16213e;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        }

What does this code do?

  • The <style> block defines CSS that controls how the page looks.
  • The body styles center everything on the page against a dark background.
  • The .container styles create a rounded card with a subtle shadow to hold your content.
  • Directly below the code you just pasted, add the element styles and body content:
        h1 {
            color: #00d4ff;
            font-size: 2.5rem;
        }
        p {
            font-size: 1.2rem;
            line-height: 1.8;
        }
        .version {
            margin-top: 20px;
            padding: 8px 16px;
            background-color: #0f3460;
            border-radius: 6px;
            display: inline-block;
            font-size: 0.9rem;
            color: #00d4ff;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Hello from Docker!</h1>
        <p>This website is running inside a Kubernetes pod.</p>
        <p>Deployed with a Deployment, exposed with a Service.</p>
        <div class="version">Version 1</div>
    </div>
</body>
</html>

What does this section do?

  • The h1, p, and .version styles control the heading color, paragraph spacing, and a small version badge.
  • The <body> section holds the actual content that appears on the page: a heading, two paragraphs, and a version label.
  • The heading says "Hello from Docker!" so you will know immediately that Docker is serving your page.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Switch back to your terminal and confirm the file exists by running:
ls

You should see index.html in the output.

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

Scroll up and confirm both sections are in your index.html file, in order:

  • Section 1: the page head with layout styles for body and .container.
  • Section 2: the element styles for h1, p, and .version, plus the <body> content ending with </html>.

Don't see index.html in the terminal?

  • Make sure you saved the file in VS Code. An unsaved file shows a dot on its tab.
  • Run pwd and confirm you are inside my-first-app. If not, run cd ~/Desktop/my-first-app.

Why isn't my index.html file showing up in the terminal?

Create a Dockerfile

Your website exists as a plain HTML file. To run it inside a container, Docker needs a recipe that says which web server to use and where to put your files. That recipe is called a Dockerfile.

  • In VS Code's Explorer sidebar, hover over the MY-FIRST-APP heading and click the New File icon.
  • Type Dockerfile (no file extension) and press Enter.
  • Paste the following code into your Dockerfile:
# Use the nginx web server as the base image
FROM nginx:1.30.4

# Copy the website into nginx's default content folder
COPY index.html /usr/share/nginx/html/index.html

What does this Dockerfile do?

  • FROM nginx:1.30.4 tells Docker to start from the official nginx web server image. This gives you a fully working web server with zero setup.
  • COPY index.html /usr/share/nginx/html/index.html places your website file into the folder where nginx looks for content to serve.

Two lines. That is all it takes to package a website into a Docker image.

  • Save Dockerfile by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Switch back to your terminal and confirm both files exist by running:
ls

You should see both Dockerfile and index.html in the output.

✔️ Awesome, I've got everything!

Great. Both files are saved and ready to build.

ⓧ I'd like to double check the full code

Here is the complete Dockerfile for comparison:

FROM nginx:1.30.4
COPY index.html /usr/share/nginx/html/index.html

Only seeing one file?

  • Make sure the file is named exactly Dockerfile with a capital D and no file extension. Names like dockerfile.txt or Dockerfile.dockerfile will not work.
  • Confirm your terminal is in the my-first-app folder with pwd.

I can only see one file when I run ls in my project folder. Help me fix this.

Build and run the Docker image

You have the website and the recipe. Now it is time to bake the image and run it. Docker will read your Dockerfile, package everything into an image, and start a container you can visit in your browser.

  • Build your Docker image by running this command in your terminal:
docker build -t my-website:v1 .

What does this command do?

  • docker build reads your Dockerfile and builds an image from it.
  • -t my-website:v1 gives the image a name (my-website) and a tag (v1) so you can reference it later.
  • The . at the end tells Docker to look for the Dockerfile in the current folder.

You should see Docker pulling the nginx:1.30.4 base image and then copying your HTML file. The last line should show your image was built successfully.

Seeing a "no such file or directory" error?

  • Make sure you are in the my-first-app folder. Run pwd to check, and ls to confirm both Dockerfile and index.html are there.
  • Make sure Docker Desktop is running. Check for the Docker icon in your menu bar.

My docker build command is failing. Help me troubleshoot.

Your image is built. Before you run it, think about this: what do you expect to see when you open http://localhost:8080 in your browser after starting the container?

  • Start a container from your image by running this command:
docker run -d -p 8080:80 --name my-container my-website:v1

What does this command do?

  • docker run creates and starts a new container from the my-website:v1 image.
  • -d runs the container in the background so you get your terminal back.
  • -p 8080:80 maps port 8080 on your Mac to port 80 inside the container, where nginx is listening.
  • --name my-container gives the container a friendly name so you can stop it later.
  • Open your browser and go to http://localhost:8080.

You should see your personal website with the heading "Hello from Docker!" on a dark background, along with a "Version 1" badge at the bottom. This page is being served by nginx running inside a Docker container on your machine.

Not seeing the website?

  • Make sure you are going to http://localhost:8080 (port 8080), not http://localhost (port 80).
  • Check that the container is running by running docker ps in your terminal. You should see my-container in the list.
  • If the container is not listed, something may have gone wrong during the run. Try running docker logs my-container to see error output.

My Docker container started but I cannot see my website in the browser. Help me fix this.

Your container proved the image works. In the next step, you will deploy this same image to Kubernetes instead. To avoid a port conflict, you need to stop and remove this container first.

  • Stop and remove the container by running this command:
docker stop my-container && docker rm my-container

Why stop and remove?

docker stop shuts down the running container. docker rm removes it entirely so the name my-container is free for later use. Your image (my-website:v1) is still saved locally. You can create new containers from it any time.

You should see the container name printed twice in your terminal, once for the stop and once for the remove.

You have a working Docker image with your website baked in. Next up, you will hand this image over to Kubernetes and see what happens when you try to deploy it to a real cluster.

Deploy to Kubernetes and Hit a Wall

Your Docker image is built and tested. In the last step, you ran your website inside a container and confirmed it loads in the browser.

Now it's time to hand that image over to Kubernetes. Kubernetes doesn't use docker run. Instead, you describe what you want in YAML files called manifests, and Kubernetes makes it happen.

In this step, get ready to:
  • Write a Deployment manifest that tells Kubernetes how to run your app.
  • Apply the manifest and confirm your app is running inside the cluster.
  • Discover a fundamental gap in how Kubernetes networking works.
Create a Deployment manifest

A Deployment is a Kubernetes resource that manages your app. It tells Kubernetes which Docker image to use, how many copies to run, and how to keep them healthy.

  • Switch back to VS Code.
  • Click File in the top menu bar.
  • Click New File... and name it deployment.yaml.
  • Make sure you save it inside your my-first-app folder.
  • Paste the following code into deployment.yaml:
# Define a Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-website
  labels:
    app: my-website
spec:
  # Run one copy of the app
  replicas: 1
  # Connect this Deployment to pods with this label
  selector:
    matchLabels:
      app: my-website
  # Blueprint for each pod
  template:
    metadata:
      labels:
        app: my-website
    spec:
      containers:
      - name: my-website
        image: my-website:v1
        ports:
        - containerPort: 80

What does this code do?

  • apiVersion and kind tell Kubernetes you want a Deployment, not some other type of resource.
  • replicas: 1 means Kubernetes will run exactly one copy of your app.
  • selector connects the Deployment to any pod labeled app: my-website.
  • template is the blueprint for each pod. It sets the label, points at your my-website:v1 Docker image, and opens port 80.
  • Save deployment.yaml.

✔️ Awesome, I've got everything!

Great. Make sure you've saved deployment.yaml inside your my-first-app folder.

ⓧ I'd like to double check the full code

Here is the complete deployment.yaml file. Make sure yours matches exactly, including indentation.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-website
  labels:
    app: my-website
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-website
  template:
    metadata:
      labels:
        app: my-website
    spec:
      containers:
      - name: my-website
        image: my-website:v1
        ports:
        - containerPort: 80
Apply the Deployment and check your pod

Your manifest is ready. Now you'll send it to Kubernetes and watch it create your app's first Pod.

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

What does this command do?

kubectl apply -f reads your YAML file and tells Kubernetes to create (or update) the resources described in it. Kubernetes receives the manifest and starts creating the pod to match your spec.

You should see deployment.apps/my-website created in your terminal.

Seeing an error instead?

  • If you see an indentation or YAML parsing error, check that every line in deployment.yaml uses spaces (not tabs) and matches the indentation in the code above exactly.
  • If you see a "no such file" error, make sure your terminal is inside the my-first-app folder where deployment.yaml is saved.
  • If you see a connection error, confirm Kubernetes is still running in Docker Desktop. The green indicator should say "Kubernetes running" at the bottom left.

Still stuck? Help me troubleshoot my kubectl apply error.

  • Confirm your pod is running by running this command:
kubectl get pods

You should see one pod listed with STATUS Running. The pod name will start with my-website- followed by a random string that Kubernetes generated.

Your app is running inside the cluster. What do you think will happen when you open http://localhost in your browser?

  • Open your browser and go to http://localhost.

Nothing loads. The page can't connect.

This is not a bug. This is exactly how Kubernetes works, and it's one of the most important concepts you'll learn today.

Why can't you see your website?

Kubernetes pods are isolated by default. Your app is running inside the cluster's private network, but nothing is routing outside traffic to it.

Think of it this way: you've moved into a building, but there's no door to the street. You need a separate Kubernetes resource called a Service to open that door and route traffic from your browser to the pod.

Your app is running, but it's trapped inside the cluster with no way for your browser to reach it. In the next step, you'll create a Service to expose it, then scale your app and watch Kubernetes heal itself.

Expose, Scale, and Self-Heal

You deployed your app to Kubernetes in the last step and hit a wall. Your pod was running, but your browser couldn't reach it.

A Kubernetes Service fixes this by creating a stable network endpoint that routes external traffic to your pods. In this step, you'll create a Service, scale up to 3 replicas, and deliberately break a pod to watch Kubernetes heal itself.

In this step, get ready to:
  • Create a Service to expose your app to the browser.
  • Scale your Deployment to 3 replicas.
  • Test self-healing by deleting a pod and watching Kubernetes replace it.
Create a Service to expose your app

Right now, your pod is running but invisible to the outside world. A Kubernetes Service acts as a stable doorway that routes incoming traffic to pods matching a specific label.

  • In VS Code, create a new file called service.yaml inside your my-first-app folder.
  • Add the following code to service.yaml:
apiVersion: v1
kind: Service
metadata:
  name: my-website
spec:
  # LoadBalancer maps to localhost on Docker Desktop
  type: LoadBalancer
  # Route traffic to pods with this label
  selector:
    app: my-website
  ports:
  - port: 80
    targetPort: 80

What does this code do?

  • type: LoadBalancer tells Kubernetes to expose this Service externally. On Docker Desktop, this maps directly to localhost.
  • selector: app: my-website matches pods with the label app: my-website. This is how the Service knows which pods to send traffic to.
  • port: 80 is the port you access in your browser. targetPort: 80 is the port nginx listens on inside each pod.
  • Save service.yaml.
  • Apply the Service to your cluster by running this command:
kubectl apply -f service.yaml

Do you think http://localhost will load your website this time?

  • Open http://localhost in your browser.

Your website loads. The same page that was unreachable moments ago now appears because the Service is routing traffic from localhost to your pod.

Website not loading at localhost?

  • Make sure you saved service.yaml before running kubectl apply. Unsaved changes won't be picked up.
  • Check that the selector in your service.yaml says app: my-website. A typo here means the Service can't find your pods.
  • Another application might be using port 80. Close any other local web servers and try again.
  • help me debug why my Kubernetes Service isn't exposing my app at localhost

✔️ Awesome, I've got everything!

Great. Double check you've saved service.yaml and move on to scaling.

ⓧ I'd like to double check the full code

Here is the complete service.yaml file. Compare it with yours:

apiVersion: v1
kind: Service
metadata:
  name: my-website
spec:
  type: LoadBalancer
  selector:
    app: my-website
  ports:
  - port: 80
    targetPort: 80
Scale to 3 replicas

Your app is running on a single pod. If that pod crashes, your website goes down. Scaling to multiple replicas means Kubernetes runs several copies of your app at the same time.

  • In VS Code, open deployment.yaml.
  • Find this line near the top of the spec section:
  replicas: 1
  • Change 1 to 3, so the line looks like this:
  replicas: 3
  • Save deployment.yaml.
  • Apply the updated Deployment by running:
kubectl apply -f deployment.yaml
  • Check your pods by running:
kubectl get pods

You should see 3 pods listed, all with STATUS Running. Kubernetes created 2 new pods to match your updated replica count.

What just happened?

When you changed replicas from 1 to 3 and reapplied the file, Kubernetes compared your desired state (3 pods) with the current state (1 pod). It automatically created 2 more pods to close the gap.

This declarative model is one of Kubernetes' most powerful ideas. You tell it what you want, and it figures out how to get there.

Not seeing 3 pods?

  • Make sure you saved deployment.yaml before running kubectl apply.
  • Verify that replicas: 3 is indented under spec: (not under template:). YAML indentation matters.
  • help me figure out why my Kubernetes Deployment isn't scaling to 3 replicas

✔️ Awesome, I've got everything!

Great. Double check you've saved deployment.yaml and move on to the self-healing test.

ⓧ I'd like to double check the full code

Here is the complete deployment.yaml file after your edit. Compare it with yours:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-website
  labels:
    app: my-website
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-website
  template:
    metadata:
      labels:
        app: my-website
    spec:
      containers:
      - name: my-website
        image: my-website:v1
        ports:
        - containerPort: 80
Test self-healing

Kubernetes doesn't just launch your pods and walk away. It actively monitors them and replaces any that disappear. This is called self-healing.

  • Copy one of the pod names from your earlier kubectl get pods output.
  • Delete that pod by running:
kubectl delete pod [[POD_NAME="your pod name from the list above"]]

Before you check your pods again, take a guess. Will you see 2 pods or 3?

  • Immediately check your pods by running:
kubectl get pods

You see 3 pods. One is either Terminating or very young (just a few seconds old). Kubernetes detected that your cluster had fewer than 3 replicas and instantly created a replacement.

  • Refresh http://localhost in your browser.

Your website is still running. The other 2 pods kept serving traffic while Kubernetes replaced the deleted one.

Why did the website stay available?

With 3 replicas, the Service routes traffic across all healthy pods. When one goes down, the remaining pods handle all requests until the replacement is ready.

This is the same principle that keeps production websites running during server failures. No manual intervention needed.

Pod not being replaced?

  • Make sure you applied the updated deployment.yaml with replicas: 3 before deleting a pod. Kubernetes only replaces pods if the Deployment expects more replicas.
  • Run kubectl get pods again after a few seconds. The replacement pod may take a moment to start.
  • help me understand why Kubernetes isn't replacing my deleted pod

Secret mission

Rolling Update with Zero Downtime

In production, you never take your app offline to deploy a new version. Can you update your website and deploy the changes to Kubernetes without any downtime?

Clean Up Your Resources

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:

  • A local Kubernetes cluster enabled inside Docker Desktop.
  • Docker images (my-website:v1 and my-website:v2) stored on your machine.
  • Kubernetes Deployment and Service objects running in the cluster.
  • Project files in the my-first-app folder on your Desktop.

Keep everything running

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

Your cluster runs inside Docker Desktop and uses some memory and CPU while active. There are no cloud costs.

Pause - I'll come back to this later

Shut down the Kubernetes cluster to free up memory while keeping your project files and Docker images on disk.

  • Open Docker Desktop.
  • Click the gear icon to open Settings.
  • Select the Kubernetes tab.
  • Uncheck Enable Kubernetes.
  • Click Apply.

Your Docker images and project files will remain on your machine for when you're ready to continue.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Navigate to your project folder by running:
cd ~/Desktop/my-first-app
  • Delete the Kubernetes Deployment and Service by running:
kubectl delete -f service.yaml && kubectl delete -f deployment.yaml
  • Remove the Docker images by running:
docker rmi my-website:v1 my-website:v2
  • Delete the project folder from your Desktop. On macOS or Linux, run:
cd ~/Desktop && rm -rf my-first-app
  • On Windows, run this instead:
cd ~\Desktop; Remove-Item -Recurse -Force my-first-app
  • Finally, disable Kubernetes in Docker Desktop.
  • Open Docker Desktop and click the gear icon to open Settings.
  • Select the Kubernetes tab.
  • Uncheck Enable Kubernetes.
  • Click Apply to reclaim local resources.

Nice Work!

Nice Work!

Nice work! You just containerized a website with Docker and deployed it to a live Kubernetes cluster running on your Mac.

You've learned how to:

  • Built a custom Docker image from a Dockerfile and ran it as a container you could see in your browser.
  • Deployed your container to a local Kubernetes cluster using a Deployment manifest, discovered that pods are isolated by default, and created a Service to expose your app to the outside world.
  • Scaled your deployment to 3 replicas and watched Kubernetes self-heal by automatically replacing a pod you deliberately deleted.
  • Completed an optional Secret Mission to push your skills even further.

Ready to quiz yourself?