Deploy a Node.js App with Kubernetes
Containerize a Node.js app with Docker and deploy it to Kubernetes.
Introduction
30 Second Summary
Every app you use daily runs inside containers on servers managed by orchestration tools. When Netflix streams a show or Uber matches you with a driver, containers are working behind the scenes to keep things running smoothly.
In this project, you will build a Node.js web server, package it into a Docker container, and deploy it to a local Kubernetes cluster. You will scale your app, perform zero-downtime updates, and manage everything from the command line.
What You'll Build
A containerized Node.js application running inside a local Kubernetes cluster that you can scale, update, and manage from your terminal.
By the end of this project, you'll have:
- A Docker container built from scratch that packages your Node.js app into a portable, deployable unit.
- A Kubernetes Deployment running your container across multiple replicas inside a local cluster powered by Minikube.
- Hands-on experience with scaling, rolling updates, and monitoring your application with zero downtime.
- Secret Mission: Add health check probes that make your app self-healing when something goes wrong.
Are there any prerequisites?
No prior experience with Docker or Kubernetes is needed. This project includes full setup instructions for every tool, starting from scratch on a Linux machine.
Install Docker Engine
Before you can containerize anything, you need Docker installed on your machine. Docker Engine is the core runtime that builds and runs containers.
In this step, you will install Docker Engine directly on Linux using the official repository. You will also configure your user account so you can run Docker commands without typing sudo every time.
In this step, get ready to:
- Install Docker Engine from the official apt repository.
- Add your user to the docker group for sudo-free commands.
- Verify Docker is working by running a test container.
Set up Docker's apt repository
Docker is not included in the default Linux package repositories. You need to add Docker's official repository first so your system knows where to download it from.
- Open your terminal.
- Add Docker's official GPG key and repository by running these commands:
# Add Docker's official GPG key
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to apt sources
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
What do these commands do?
- The first block downloads Docker's GPG signing key so your system can verify that packages actually come from Docker.
- The second block creates a new repository source file that points apt to Docker's servers.
- The final sudo apt update refreshes your package list so it includes Docker's packages.
Not on Ubuntu?
If you are using a different Linux distribution (Debian, Fedora, CentOS, RHEL), follow the matching guide on the official Docker Engine install page. The steps are similar but use different repository URLs and package managers.
- Enter your password if prompted. The terminal will not show characters as you type.
Seeing a permission denied error?
Make sure you are typing sudo before each command. Without it, your system blocks changes to protected directories.
help me fix a permission denied error when adding Docker's apt repository
Install Docker Engine packages
Now that your system knows about Docker's repository, you can install the engine itself along with its companion tools.
- Install Docker Engine and its plugins by running:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
What gets installed?
- docker-ce is the Docker Engine daemon that builds and runs containers.
- docker-ce-cli is the command-line tool you use to interact with Docker.
- containerd.io is the lower-level container runtime that Docker depends on.
- docker-buildx-plugin and docker-compose-plugin add multi-platform builds and multi-container orchestration.
- When prompted to confirm the installation, type Y and press Enter.
Package not found?
This means the repository was not added correctly. Go back and re-run the commands from the previous substep. Make sure sudo apt update completed without errors.
help me fix a package not found error when installing Docker Engine on Linux
Add your user to the docker group
By default, only the root user can run Docker commands. Adding your user to the docker group lets you skip typing sudo before every Docker command.
- Create the docker group and add your user to it by running these commands:
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
What does this do?
- sudo groupadd docker creates a new system group called docker. If the group already exists, that is fine.
- sudo usermod -aG docker $USER adds your current user to that group.
- newgrp docker activates the new group membership in your current terminal session without requiring a full logout.
- Verify Docker works without sudo by running:
docker run hello-world
You should see a message starting with "Hello from Docker!" followed by an explanation of what just happened.
What is docker run hello-world?
This command downloads a tiny test image from Docker Hub and runs it in a container.
The container prints a success message and exits. If you see the message, your entire Docker installation (daemon, CLI, networking, and image pulling) is working correctly.
Seeing a permission denied error on the socket?
If you see an error mentioning the Docker socket, the group change has not taken effect yet. Try logging out and back in to your Linux session, then run docker run hello-world again.
help me fix a Docker socket permission denied error after adding my user to the docker group
Why Docker Engine instead of Docker Desktop?
Docker Desktop on Linux runs inside a virtual machine, which adds overhead. Docker Engine runs natively on your system and is the standard for Linux servers.
For learning containerization fundamentals, Docker Engine gives you a more authentic experience that translates directly to production environments.
Docker is installed and running. Next up, you will install Minikube and kubectl to create a local Kubernetes cluster.
Install Minikube and kubectl
Docker is installed and working. Now you need something to run and manage containers at scale. That's what Kubernetes does.
In this step you'll install two tools. Minikube runs a local Kubernetes cluster on your machine. kubectl is the command-line tool that talks to that cluster.
In this step, get ready to:
- Install the Minikube binary on your machine.
- Install kubectl, the Kubernetes command-line tool.
- Start a local Kubernetes cluster and verify it is running.
Download and install Minikube
Minikube creates a single-node Kubernetes cluster inside Docker containers on your machine. No virtual machine or cloud account required.
- Check if Minikube is already installed by running this command:
minikube version
✔️ I see a version number
Minikube is already installed. Skip ahead to the next substep where you install kubectl.
ⓧ Command not found
Minikube is not installed yet. Follow the instructions below to install it.
- Download and install the Minikube binary by running these commands:
curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube && rm minikube-linux-amd64
What do these commands do?
- curl -LO downloads the Minikube binary file from GitHub.
- sudo install moves the binary into /usr/local/bin/ so you can run it from anywhere.
- rm minikube-linux-amd64 cleans up the downloaded file.
- Confirm the install worked by running:
minikube version
You should see a version number printed in your terminal.
Seeing permission denied?
Make sure you included sudo in the install command. Without it, your user account doesn't have permission to write to /usr/local/bin/.
Help me troubleshoot this
Download and install kubectl
kubectl is the standard command-line tool for interacting with any Kubernetes cluster. Every command you run against your cluster goes through kubectl.
- Check if kubectl is already installed by running this command:
kubectl version --client
✔️ I see a version number
kubectl is already installed. Skip ahead to the next substep where you start the cluster.
ⓧ Command not found
kubectl is not installed yet. Follow the instructions below to install it.
- Download the latest kubectl binary by running this command:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- Install the kubectl binary by running this command:
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
What do these commands do?
The first command downloads the kubectl binary that matches the latest stable Kubernetes release. The nested curl call fetches the version string automatically.
The second command installs the binary with the correct ownership and permissions so any user can run it.
- Confirm the install worked by running:
kubectl version --client
You should see a client version line printed in your terminal.
Seeing permission denied or command not found?
Make sure you included sudo in the install command. Also check that the download completed successfully before installing. If curl failed silently, the binary file may be empty.
Help me troubleshoot this
Start a Minikube cluster
Both tools are installed. Now you can start a local Kubernetes cluster using Docker as the runtime.
Why Docker as the driver?
Minikube can run Kubernetes inside a virtual machine or inside Docker containers. The Docker driver runs Kubernetes components as containers directly on your machine.
It does not require virtualization to be enabled and is the fastest option on Linux.
- Start your Minikube cluster by running this command:
minikube start --driver=docker
What does this command do?
This tells Minikube to create a single-node Kubernetes cluster using Docker containers. Minikube pulls the required images and configures everything automatically.
The first run takes a few minutes because Minikube downloads Kubernetes components. Subsequent starts are much faster.
- Verify your cluster is healthy by running:
kubectl cluster-info
You should see output showing the Kubernetes control plane running at a local address.
Cluster not starting?
- Make sure Docker is running. Run docker ps to confirm. If you see an error, start the Docker daemon first.
- Minikube requires at least 2 CPUs and 2 GB of RAM. If your machine is low on resources, close other applications and try again.
- If you see a permissions error, confirm your user is in the docker group by running groups in your terminal.
Help me debug this
Your local Kubernetes cluster is up and running. Next up, you'll build a Node.js app to deploy into it.
Build Your Node.js App
Your Kubernetes cluster is up and running. Now you need an application to deploy into it.
In this step, you'll build a simple Node.js web server using Express. The server returns a JSON message that includes the container's hostname, which helps you see load balancing in action when you scale to multiple containers later.
In this step, get ready to:
- Create a project folder and set up your dependencies.
- Write an Express server that returns JSON with hostname info.
- Test the app locally in your browser.
Create your project folder and set up dependencies
Before writing any code, you need a project folder and a package.json file that declares your app's dependencies.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir k8s-demo-app
cd k8s-demo-app
- Create a file called package.json inside your k8s-demo-app folder and paste the following content:
{
"name": "k8s-demo-app",
"version": "1.0.0",
"description": "A simple Node.js app for learning Docker and Kubernetes",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.21.0"
}
}
What does this file do?
The package.json file is your app's manifest. It tells Node.js your app's name, entry point (index.js), and which packages it depends on.
The dependencies section lists Express as a required package. When you run the install command next, it downloads Express and everything Express needs into a node_modules folder.
- Save package.json.
- Install the project dependencies by running:
npm install
- You should see output showing packages being added, ending with a line like added X packages.
Seeing an error from npm install?
- Make sure you saved package.json before running the install command.
- Check that your package.json has valid JSON syntax. A missing comma or extra bracket will cause an error.
- Confirm you are inside the k8s-demo-app folder by running pwd.
Why won't npm install my dependencies?
✔️ Awesome, I've got everything!
Great. Double check you have saved your package.json and that a node_modules folder now exists inside k8s-demo-app.
ⓧ I'd like to double check the full code
Here is the complete package.json for comparison:
{
"name": "k8s-demo-app",
"version": "1.0.0",
"description": "A simple Node.js app for learning Docker and Kubernetes",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.21.0"
}
}
Write the Express server
Your app needs a server that listens for HTTP requests and responds with JSON. You will use Express to handle routing with minimal boilerplate.
- Create a file called index.js inside your k8s-demo-app folder and paste the following code:
// Import the Express framework and the OS module
const express = require('express');
const os = require('os');
// Create the app instance and define the port
const app = express();
const PORT = 3000;
// Main route - returns JSON with the container's hostname
app.get('/', (req, res) => {
res.json({
message: 'Hello from Kubernetes!',
hostname: os.hostname(),
timestamp: new Date().toISOString()
});
});
// Health check endpoint for Kubernetes probes
app.get('/healthz', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
// Start the server and log a confirmation message
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
What does this code do?
- The / route returns a JSON object with a greeting, the machine's hostname (os.hostname()), and a timestamp. When this runs inside Kubernetes, the hostname changes per container, so you can see which pod handled the request.
- The /healthz route always returns a 200 status. Kubernetes uses endpoints like this to check whether your app is alive and ready to serve traffic.
- The app.listen call starts the server on port 3000 and logs a confirmation.
- Save index.js.
Syntax error when you save?
- Make sure every opening parenthesis, bracket, and curly brace has a matching closing one.
- Check that backticks (`) surround the template literal in console.log. Single quotes will not work there.
Help me fix a syntax error in my Express server code.
✔️ Awesome, I've got everything!
Great. Double check you have saved index.js in the same folder as package.json.
ⓧ I'd like to double check the full code
Here is the complete index.js for comparison:
const express = require('express');
const os = require('os');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from Kubernetes!',
hostname: os.hostname(),
timestamp: new Date().toISOString()
});
});
app.get('/healthz', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Test the app locally
Before containerizing anything, confirm the app runs correctly on your machine. This catches typos and missing dependencies early.
- Start the server by running:
node index.js
- You should see this message in your terminal:
Server running on port 3000
- Open your browser and go to http://localhost:3000.
- You should see a JSON response with a message, hostname, and timestamp field.
What should I see?
Your browser displays something like:
{
"message": "Hello from Kubernetes!",
"hostname": "your-machine-name",
"timestamp": "2026-05-16T12:00:00.000Z"
}
The hostname shows your computer's name right now. Once this app runs inside Kubernetes, it shows the pod name instead.
- Go back to your terminal and press Ctrl+C to stop the server.
Don't see the JSON response?
- If the terminal says Cannot find module 'express', you skipped the npm install step. Run it inside your k8s-demo-app folder.
- If the browser says the site cannot be reached, make sure the terminal still shows Server running on port 3000 and that you are visiting http://localhost:3000 (not https).
Why can't I see my Express app in the browser?
Your app is working locally. Next up, you'll package it into a Docker container so it can run anywhere, not just on your machine.
Containerize with Docker
Your Node.js app runs perfectly on your machine. But what happens when you need to run it on a different server, a teammate's laptop, or inside a Kubernetes cluster? The answer is containerization.
In this step, you'll write a Dockerfile that packages your app into a portable container image. Then you'll build and run that image to prove it works completely isolated from your local system.
In this step, get ready to:
- Write a Dockerfile that defines how your app is packaged.
- Build a Docker image from your Dockerfile.
- Run the container locally and verify it serves your app.
Write a Dockerfile using node:18-alpine as the base image
A Dockerfile is a recipe that tells Docker exactly how to build your application into a container image. Each line is an instruction that adds a layer to the image.
Before writing the Dockerfile, you need a .dockerignore file. This tells Docker which files to skip when copying your project into the image.
- In your text editor, create a new file called .dockerignore inside your k8s-demo-app folder and add this content:
node_modules
npm-debug.log
Why exclude node_modules?
Your local node_modules folder contains packages built for your operating system. The container will run its own npm install inside a different OS (Alpine Linux), so it needs its own copy.
Excluding it also makes the build faster because Docker does not need to copy hundreds of megabytes of dependencies into the image.
- Now create a file called Dockerfile (no file extension) in the same k8s-demo-app folder and add this content:
# Use a lightweight Node.js base image
FROM node:18-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy package files first (for better caching)
COPY package*.json ./
# Install only production dependencies
RUN npm install --production
# Copy the rest of the application code
COPY . .
# Document which port the app uses
EXPOSE 3000
# Start the app when the container runs
CMD ["node", "index.js"]
What does this Dockerfile do?
- FROM node:18-alpine starts from a pre-built image that has Node.js 18 installed on Alpine Linux (a tiny, secure Linux distribution).
- WORKDIR /app creates and moves into a folder called /app inside the container.
- COPY package*.json ./ copies your package files first, so Docker can cache the npm install step when your code changes but dependencies do not.
- COPY . . copies the rest of your project (minus anything in .dockerignore).
- CMD ["node", "index.js"] is the command that runs when a container starts from this image.
- Save both .dockerignore and Dockerfile.
- Confirm the files exist by running:
ls
You should see Dockerfile and .dockerignore listed alongside index.js, package.json, and node_modules.
Don't see the Dockerfile?
Make sure the file is named exactly Dockerfile with a capital D and no file extension. It is not dockerfile.txt or Dockerfile.txt.
Files starting with a dot (like .dockerignore) are hidden by default on Linux. Use ls -a to see hidden files.
help me find my Dockerfile and .dockerignore files
Build the Docker image
With your Dockerfile written, you can now build it into a Docker image. An image is a snapshot of your application and everything it needs to run.
- Make sure your terminal is inside the k8s-demo-app folder, then build the image by running this command:
docker build -t k8s-demo-app:v1 .
What does this command do?
- docker build reads your Dockerfile and builds an image from it.
- -t k8s-demo-app:v1 tags the image with a name (k8s-demo-app) and a version (v1). You will use this tag to reference the image later.
- The . at the end tells Docker to use the current directory as the build context (the files it can access).
You should see Docker processing each layer of your Dockerfile. The build finishes with a success message and your tagged image name.
- Verify the image was created by running:
docker images
You should see k8s-demo-app listed with the tag v1.
Build failed?
If you see a permission denied error, make sure you added your user to the docker group in Step 1. Try running newgrp docker and then retry the build command.
If the error mentions the Dockerfile not being found, confirm you are running the command from inside the k8s-demo-app folder where your Dockerfile lives.
help me fix my Docker build error
Run the container locally to verify it works
You have an image. Now you need to prove it actually works by running it as a container.
- If your Node.js app from the previous step is still running, stop it with Ctrl+C so port 3000 is free.
- Start a container from your image by running this command:
docker run -p 3000:3000 k8s-demo-app:v1
What does this command do?
- docker run creates and starts a new container from the specified image.
- -p 3000:3000 maps port 3000 on your machine to port 3000 inside the container. Without this, you could not reach the app from your browser.
- k8s-demo-app:v1 is the image name and tag you built in the previous substep.
You should see Server running on port 3000 printed in your terminal. This output comes from your app running inside the container.
- Open a new browser tab and visit http://localhost:3000.
You should see the same JSON response as before, with a message, hostname, and timestamp. The hostname value is now a random container ID instead of your machine's name. That proves the app is running inside the container, not on your host system.
- Go back to your terminal and press Ctrl+C to stop the container.
Port already in use?
If you see an error about port 3000 being in use, your Node.js app from the previous step may still be running. Find and stop it by running lsof -i :3000 to find the process, then kill <PID> to stop it.
help me fix a port conflict when running my Docker container
✔️ Awesome, I've got everything!
Great. Double check you have saved both Dockerfile and .dockerignore. Your Docker image k8s-demo-app:v1 is built and ready to deploy.
ⓧ I'd like to double check the full code
Here are all the files in your project at this point.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
node_modules
npm-debug.log
const express = require('express');
const os = require('os');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from Kubernetes!',
hostname: os.hostname(),
timestamp: new Date().toISOString()
});
});
app.get('/healthz', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
{
"name": "k8s-demo-app",
"version": "1.0.0",
"description": "A simple Node.js app for learning Docker and Kubernetes",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.21.0"
}
}
Your app is now packaged into a portable container image. Next up, you'll deploy this container into your Kubernetes cluster and let Kubernetes manage it for you.
Deploy to Kubernetes
Your Docker image is built and tested locally. Now it's time to hand it over to Kubernetes, the orchestration layer that will run, manage, and scale your containerized app.
In this step, you'll load your image into Minikube, write the YAML manifests that tell Kubernetes exactly how to run your app, and apply them to bring your app live inside the cluster.
In this step, get ready to:
- Load your Docker image into the Minikube cluster.
- Write Kubernetes Deployment and Service manifests.
- Apply the manifests and access your running app.
Load your Docker image into Minikube
Minikube runs its own isolated container runtime. Your locally built Docker image is invisible to it unless you explicitly load it in.
- Load your k8s-demo-app:v1 image into Minikube by running this command:
minikube image load k8s-demo-app:v1
This command produces no output on success. It may take 30-60 seconds to transfer the image.
- Confirm the image is available inside Minikube by running:
minikube image ls | grep k8s-demo
You should see a line containing k8s-demo-app in the output.
Not seeing your image?
- Make sure Minikube is running. Run minikube status to check.
- Confirm the image exists locally with docker images and look for k8s-demo-app:v1 in the list.
- help me troubleshoot why minikube image load is not working
Write your Kubernetes Deployment manifest
Kubernetes uses YAML files called manifests to define what should run in the cluster. A Deployment tells Kubernetes how many copies (replicas) of your container to run and how to update them.
- Create a file called deployment.yaml in your k8s-demo-app folder by running:
touch deployment.yaml
- Open deployment.yaml in your text editor and paste in the following Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-demo-app
labels:
app: k8s-demo
spec:
replicas: 3
selector:
matchLabels:
app: k8s-demo
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo-app:v1
ports:
- containerPort: 3000
What does this manifest do?
- replicas: 3 tells Kubernetes to run three copies of your container at all times. If one crashes, Kubernetes automatically replaces it.
- selector and labels connect the Deployment to the pods it manages. The label app: k8s-demo acts as the link.
- image: k8s-demo-app:v1 points to the Docker image you just loaded into Minikube.
- containerPort: 3000 declares that the container listens on port 3000.
Add a Service to expose your app
A Service gives your pods a stable network address. Without it, there's no way to reach your app from outside the cluster.
- In deployment.yaml, add a YAML document separator and the Service definition below the Deployment. Paste this at the very bottom of the file:
---
apiVersion: v1
kind: Service
metadata:
name: k8s-demo-service
spec:
type: NodePort
selector:
app: k8s-demo
ports:
- port: 3000
targetPort: 3000
nodePort: 30001
What does the Service do?
- type: NodePort exposes your app on a port accessible from outside the cluster. NodePort values must be between 30000-32767.
- selector: app: k8s-demo routes traffic to any pod with the matching label.
- port: 3000 is the port the Service listens on. targetPort: 3000 is the port on the container. nodePort: 30001 is the external port you'll use to access it.
- Save deployment.yaml.
✔️ 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: apps/v1
kind: Deployment
metadata:
name: k8s-demo-app
labels:
app: k8s-demo
spec:
replicas: 3
selector:
matchLabels:
app: k8s-demo
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo-app:v1
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: k8s-demo-service
spec:
type: NodePort
selector:
app: k8s-demo
ports:
- port: 3000
targetPort: 3000
nodePort: 30001
Apply your manifests and access the app
Your manifests describe the desired state. Now you'll tell Kubernetes to make it real.
- Apply your manifests to the cluster by running:
kubectl apply -f deployment.yaml
You should see two lines confirming the resources were created:
deployment.apps/k8s-demo-app created
service/k8s-demo-service created
- Check that your pods are running by running:
kubectl get pods
You should see three pods with a STATUS of Running. It may take a few seconds for all three to start.
Why three pods?
Your manifest specified replicas: 3. Kubernetes created three identical copies of your container. If one fails, Kubernetes automatically replaces it to maintain the count. This is the foundation of high availability.
- Access your app through Minikube by running:
minikube service k8s-demo-service
This opens your app in the browser (or prints the URL). You should see the JSON response with a message, hostname, and timestamp.
What is the hostname field?
The hostname in the response is the pod name. Each replica has a unique hostname. Refresh the page a few times and you may see different hostnames as the Service load-balances across your three pods.
Pods stuck in ImagePullBackOff or ErrImagePull?
This means Kubernetes cannot find your Docker image. The image needs to be loaded into Minikube's own image store.
- Run minikube image ls | grep k8s-demo to confirm the image is loaded.
- If it's missing, run minikube image load k8s-demo-app:v1 again, then delete and reapply your deployment.
- help me fix ImagePullBackOff errors in my Kubernetes deployment
Your app is now live inside a Kubernetes cluster with three replicas. Next up, you'll learn to scale it even further and push updates with zero downtime.
Scale and Update Your App
Your app is running in Kubernetes with 3 replicas handling traffic. In production, traffic demands change constantly.
Kubernetes makes it trivial to add more copies of your app and push updates without any downtime. In this step, you'll scale your deployment up and perform a rolling update to deploy a new version of your app.
In this step, get ready to:
- Scale your deployment from 3 to 5 replicas.
- Perform a rolling update with a new version of your app.
- Verify zero-downtime during the update.
Scale your deployment to 5 replicas
Right now your deployment runs 3 pods. Kubernetes lets you change that number with a single command, and it handles spinning up new pods automatically.
- Scale your deployment to 5 replicas by running this command:
kubectl scale deployment k8s-demo-app --replicas=5
What does this command do?
This tells Kubernetes to increase the number of running copies (replicas) of your k8s-demo-app deployment from 3 to 5. Kubernetes creates 2 new pods to meet the new target.
- Confirm all 5 pods are running by running this command:
kubectl get pods
You should see 5 pods listed, all with a STATUS of Running. The 2 newest pods will have a younger AGE value than the original 3.
Pods stuck in Pending?
If any pods show a Pending status, your Minikube cluster may not have enough resources. Run minikube stop followed by minikube start --memory=4096 to restart with more memory allocated.
Help me troubleshoot pods stuck in Pending status after scaling my deployment
Update your app and perform a rolling update
Scaling is one thing. Deploying new code without downtime is another. A rolling update replaces pods one at a time with the new version, so your app stays available throughout.
- In index.js, find this line:
message: 'Hello from Kubernetes!',
- Replace 'Hello from Kubernetes!' with 'Hello from Kubernetes! v2', so the line looks like this:
message: 'Hello from Kubernetes! v2',
- Save index.js.
Now you need to build a new Docker image with this updated code and load it into Minikube.
- Build a new Docker image tagged as v2 by running this command:
docker build -t k8s-demo-app:v2 .
- Load the new image into Minikube by running:
minikube image load k8s-demo-app:v2
- Trigger the rolling update by telling Kubernetes to use the new image:
kubectl set image deployment/k8s-demo-app k8s-demo=k8s-demo-app:v2
What does this command do?
This updates the container named k8s-demo inside the k8s-demo-app deployment to use the k8s-demo-app:v2 image. Kubernetes begins replacing old pods with new ones, one at a time.
Seeing ImagePullBackOff or ErrImagePull?
This means Minikube cannot find the k8s-demo-app:v2 image. Make sure you ran minikube image load k8s-demo-app:v2 after building the image. The image must be loaded into Minikube's internal registry before the cluster can use it.
Help me fix ImagePullBackOff errors in my Minikube deployment
Watch the rolling update and verify zero downtime
Kubernetes performs the update gradually. You can watch it happen in real-time.
- Watch the rollout progress by running this command:
kubectl rollout status deployment/k8s-demo-app
You should see progress messages as Kubernetes replaces old pods with new ones. When the update finishes, you will see a message confirming the deployment was successfully rolled out.
What is a rolling update?
Instead of shutting down all old pods and starting new ones (which causes downtime), Kubernetes terminates and replaces pods one at a time. At every moment during the update, some pods are still running and serving traffic.
This is why it's called "zero-downtime" deployment. Your users never experience an outage, even during updates.
- Verify the new version is running by accessing your app:
minikube service k8s-demo-service --url
- Copy the URL from the output and open it in your browser, or run curl with that URL.
You should see the updated JSON response with the message Hello from Kubernetes! v2. The hostname field shows which pod served your request.
Still seeing the old message?
Run kubectl get pods to check that all pods have restarted. If some old pods are still running, the rollout may not be complete yet. Wait a few seconds and try again.
Also confirm that you saved index.js before rebuilding the Docker image. If the image was built with the old code, the update will succeed but the message won't change.
Help me debug why my rolling update isn't showing the new version
You just scaled your app on the fly and deployed a new version with zero downtime. These are the same operations that production teams run every day. Next up, you'll tackle the Secret Mission and make your app self-healing.
Secret mission
Add Self-Healing with Health Probes
Your app is running in Kubernetes, but Kubernetes has no idea if it is actually healthy. If your server freezes or enters a broken state, Kubernetes will keep routing traffic to it. In this secret mission, you will add health check probes that let Kubernetes detect and automatically restart unhealthy containers.
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 Minikube cluster (running as Docker containers).
- Docker images stored locally.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your cluster.
- Your Minikube cluster stays active and uses RAM and CPU while running.
- Docker images remain cached on disk.
Pause - I'll come back to this later
Shut down running processes to free up memory and CPU, but keep everything on disk so you can resume later.
- Stop the Minikube cluster by running:
minikube stop
- When you are ready to come back, start the cluster again by running:
minikube start
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees up disk space and removes the cluster entirely.
- Delete the Minikube cluster by running:
minikube delete
- Remove all unused Docker images, containers, and build cache by running:
docker system prune -a
What does this remove?
This command removes all Docker images not currently used by a running container, along with stopped containers, unused networks, and build cache. Volumes are not affected unless you add the --volumes flag.
That's a Wrap!
That's a Wrap!
Nice work! You just went from zero to running a containerized app in Kubernetes. That is a core DevOps workflow that production teams use every day.
You've learned how to:
- Built a Node.js web server and packaged it into a Docker container image using a Dockerfile.
- Deployed your containerized app to a local Kubernetes cluster with Minikube using Deployment and Service manifests.
- Scaled your app and performed a zero-downtime rolling update with kubectl commands.
- Added liveness and readiness probes for self-healing in the Secret Mission.
Ready to quiz yourself?