Local CI/CD Pipeline with Jenkins
Build a local DevOps pipeline with Docker, Kubernetes, Terraform, and Jenkins.
Introduction
30 Second Summary
Every developer writes code, but getting that code safely and automatically from your laptop to a running server is where professional DevOps begins. Companies use automated pipelines to build, test, and deploy their applications without any manual steps.
In this project, you will build a complete local CI/CD pipeline that connects four industry-standard tools. You will containerize an application, automate its build process, define your infrastructure as code, and deploy it to a local Kubernetes cluster.
What You'll Build
A fully functional local CI/CD pipeline where a code change triggers an automated build and deployment to a Kubernetes cluster, mirroring how production DevOps teams work.
By the end of this project, you'll have:
- A Dockerfile that containerizes a simple Node.js web application into a portable Docker image.
- A Jenkins server running locally in Docker with a configured CI/CD pipeline that automates your entire build and deploy process.
- Terraform configuration files that define a Kubernetes namespace, deployment, and service as Infrastructure as Code.
- A working end-to-end pipeline where Jenkins builds a Docker image and Terraform deploys it to a local Minikube cluster.
- Secret Mission: Add a rolling update strategy to your deployment and watch Kubernetes perform zero-downtime updates.
Are there any prerequisites?
This project assumes you have Docker, Minikube (with kubectl), Terraform, and VS Code already installed. You should also have basic command-line knowledge and an understanding of what containers are.
Verify Your Tools and Start Minikube
Before building your pipeline, you need to confirm that all four tools in your DevOps stack are working correctly on your machine.
In this step, you'll verify Docker, Minikube, kubectl, and Terraform are accessible from your terminal. Then you'll start a local Kubernetes cluster so it's ready for deployments in later steps.
In this step, get ready to:
- Verify Docker, Minikube, kubectl, and Terraform are accessible from your terminal.
- Start a Minikube cluster using the Docker driver.
- Confirm the cluster is running and kubectl can communicate with it.
Verify your DevOps tools
Each tool in your pipeline has a specific job. Docker builds images, Minikube runs your local Kubernetes cluster, kubectl sends commands to the cluster, and Terraform defines your infrastructure as code.
- In VS Code, click Terminal in the top menu bar.
- Click New Terminal.
- Check that all four tools are accessible by running these commands one at a time:
docker --version
minikube version
kubectl version --client
terraform --version
Each command should print a version number. The exact version doesn't matter as long as the command is recognized.
Command not found?
If any command returns command not found, the tool isn't in your system PATH. Reinstall it using the official installation guide for your operating system.
Make sure Docker Desktop is running (not just installed) before checking docker --version. On macOS, you may need to open Docker Desktop from your Applications folder first.
Help me troubleshoot a missing DevOps tool on my machine
Start your Minikube cluster
Minikube creates a single-node Kubernetes cluster on your local machine. It uses Docker as the underlying driver to run the cluster inside a container.
- Start your Minikube cluster with the Docker driver by running this command:
minikube start --driver=docker
What does this command do?
minikube start creates and boots a local Kubernetes cluster. The --driver=docker flag tells Minikube to run the cluster node as a Docker container on your machine.
This takes 1-2 minutes the first time. Minikube downloads required images, starts the container, and configures kubectl to point at the new cluster.
Wait for the command to finish. You should see output ending with a message that the cluster is ready to use.
Minikube failed to start?
Make sure Docker Desktop is running before you run minikube start. Minikube needs the Docker daemon active to create the cluster container.
If you see a resource error, check that your machine has at least 2 CPUs and 2GB of free RAM available for Minikube.
Help me fix a Minikube start failure
Confirm kubectl can talk to your cluster
Starting Minikube automatically configures kubectl to communicate with your new cluster. Let's confirm the connection works.
- Check that your cluster node is ready by running:
kubectl get nodes
You should see one node named minikube with a status of Ready.
- Verify the cluster details by running:
kubectl cluster-info
You should see output showing that the Kubernetes control plane is running at a local address.
Why does this matter?
kubectl is your command-line interface for Kubernetes. If it can reach the cluster, it means Terraform will also be able to reach it later when provisioning resources.
Minikube stores the connection details in a file at ~/.kube/config. Both kubectl and the Terraform Kubernetes provider read from this file.
Node not showing Ready?
If the node status says NotReady, wait 30 seconds and run kubectl get nodes again. The cluster may still be initializing.
If kubectl returns a connection error, run minikube status to confirm the cluster is still running. If it stopped, run minikube start --driver=docker again.
Help me fix kubectl communication with Minikube
Your local Kubernetes cluster is up and running. Next up, you'll create the application that your pipeline will build and deploy.
Create the Application and Dockerfile
Your tools are verified and your Minikube cluster is running.
Now it is time to create the application that your pipeline will build and deploy. You will write a simple Node.js web server, then package it into a Docker image so it can run anywhere.
In this step, get ready to:
- Create a Node.js HTTP server that returns a JSON response.
- Write a Dockerfile to containerize the application.
- Build and test the Docker image locally.
Create the project directory and Node.js application
Every pipeline needs source code to work with. You will create a minimal HTTP server that returns JSON data, which is enough to prove your pipeline works end-to-end without getting bogged down in application complexity.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and the app subdirectory, then move into the project folder by running these commands:
mkdir -p devops-pipeline/app
cd devops-pipeline
- Confirm the folders exist by running:
ls
You should see app in the output.
Don't see the app folder?
Make sure you ran both commands. The first command creates the folder, and the second moves you into it. Run pwd to confirm you are inside devops-pipeline.
help me figure out why my project directory wasn't created
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the devops-pipeline folder on your Desktop, then click Open.
- In the Explorer sidebar, right-click the app folder and select New File.
- Name the file index.js and press Enter.
- Paste the following code into app/index.js:
const http = require('http');
const PORT = 3000;
// Create an HTTP server that responds with JSON
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Hello from the DevOps Pipeline!',
version: 'v1',
timestamp: new Date().toISOString()
}));
});
// Start listening on port 3000
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
What does this code do?
- http.createServer creates a basic web server using Node.js built-in http module. No external packages needed.
- Every request gets a JSON response containing a message, a version tag (v1), and the current timestamp.
- The version field lets you verify which version of your app is running after a deployment.
- Save app/index.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Make sure app/index.js is saved and move on to the Dockerfile.
ⓧ I'd like to double check the full code
const http = require('http');
const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Hello from the DevOps Pipeline!',
version: 'v1',
timestamp: new Date().toISOString()
}));
});
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Write the Dockerfile
A Dockerfile is a recipe that tells Docker how to package your application into a container image. This image can then run identically on any machine, which is exactly what your pipeline needs.
- In the Explorer sidebar, right-click the app folder and select New File.
- Name the file Dockerfile (no extension) and press Enter.
- Paste the following code into app/Dockerfile:
# Use a lightweight Node.js base image
FROM node:22-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy the application file into the container
COPY index.js .
# Document the port the app listens on
EXPOSE 3000
# Start the application when the container runs
CMD ["node", "index.js"]
What does this Dockerfile do?
- FROM node:22-alpine uses a minimal Node.js image based on Alpine Linux. It is only ~180MB, keeping builds fast.
- WORKDIR /app sets the default directory inside the container so subsequent commands run from /app.
- COPY index.js . copies your application code into the container image.
- CMD ["node", "index.js"] tells Docker what command to run when the container starts.
- Save app/Dockerfile by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Make sure app/Dockerfile is saved and move on to building the image.
ⓧ I'd like to double check the full code
FROM node:22-alpine
WORKDIR /app
COPY index.js .
EXPOSE 3000
CMD ["node", "index.js"]
Build and test the Docker image locally
Before your pipeline can deploy the image, you need to confirm it builds correctly and the application responds.
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, same on macOS and Windows).
- Make sure you are in the devops-pipeline folder. If not, run:
cd ~/Desktop/devops-pipeline
- Build the Docker image by running this command:
docker build -t pipeline-app:v1 ./app
What does this command do?
- -t pipeline-app:v1 tags the image with the name pipeline-app and version v1.
- ./app tells Docker to look for the Dockerfile inside the app directory.
You should see Docker pull the node:22-alpine base image (first time only) and then output several build steps ending with a success message.
Build failed?
Check that your Dockerfile is inside the app folder (not in the project root). Also confirm Docker is running by checking docker info in your terminal.
help me debug my Docker build failure
- Run a container from your new image to test it:
docker run -d -p 3000:3000 --name test-container pipeline-app:v1
What does this command do?
- -d runs the container in the background (detached mode).
- -p 3000:3000 maps port 3000 on your machine to port 3000 inside the container.
- --name test-container gives the container a name so you can easily stop it later.
- Test the application by sending a request:
curl http://localhost:3000
You should see a JSON response like this:
{"message":"Hello from the DevOps Pipeline!","version":"v1","timestamp":"2026-06-06T..."}
Not seeing the JSON response?
Run docker ps to check if the container is running. If the container exited immediately, run docker logs test-container to see the error output.
A common cause is a typo in index.js that prevents Node.js from starting. Fix the typo, rebuild the image, and try again.
help me figure out why my Docker container isn't responding
- Stop and remove the test container now that you have confirmed it works:
docker stop test-container && docker rm test-container
You do not need this container running anymore. Your pipeline will build and deploy fresh containers automatically.
Your application is containerized and ready. Next up, you will launch Jenkins in Docker so it can automate building this image for you.
Launch Jenkins in Docker
Your Docker image is built and tested. Now you need something to automate that build process so you don't have to run docker build manually every time your code changes.
Jenkins is a CI/CD automation server that can trigger builds, run tests, and deploy applications on your behalf. You'll run Jenkins itself as a Docker container, which keeps your host machine clean and teaches another Docker concept at the same time.
In this step, get ready to:
- Run the Jenkins container with Docker socket access.
- Complete the Jenkins initial setup wizard.
- Install the Docker Pipeline plugin.
Run the Jenkins container with Docker socket access
For Jenkins to build Docker images, it needs to communicate with your host's Docker daemon. You achieve this by mounting the Docker socket file into the Jenkins container.
- Start the Jenkins container by running this command:
docker run -d --name jenkins \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:2.555.2-lts-jdk21
What does this command do?
- -d runs the container in detached mode (in the background).
- --name jenkins gives the container a friendly name you can reference later.
- -p 8080:8080 maps Jenkins' web interface port to your host machine.
- -v /var/run/docker.sock:/var/run/docker.sock mounts the Docker socket so Jenkins can issue Docker commands to your host's daemon.
- -v jenkins_home:/var/jenkins_home persists Jenkins data (plugins, jobs, config) in a Docker volume.
- Confirm the container is running by running:
docker ps
You should see a container named jenkins with status Up and port 0.0.0.0:8080->8080/tcp listed.
Container not starting?
- If you see a port conflict error, another process is using port 8080. Stop it or change the port mapping to -p 9090:8080 (and use port 9090 for all browser steps below).
- If you see a name conflict, a previous container named jenkins already exists. Remove it with docker rm -f jenkins and try again.
Help me debug my Jenkins container startup issue.
The Jenkins container is running, but it doesn't include the Docker CLI by default. You need to install it so Jenkins can execute docker build commands later in your pipeline.
- Install the Docker CLI inside the Jenkins container by running:
docker exec -u root jenkins bash -c "apt-get update && apt-get install -y docker.io"
Why install Docker CLI inside Jenkins?
Mounting the Docker socket gives Jenkins a communication channel to your host's Docker daemon. But without the Docker CLI binary, Jenkins has no way to send commands through that channel. Installing docker.io adds the CLI so Jenkins can run docker build from pipeline scripts.
- Fix the Docker socket permissions so the Jenkins user can access it:
docker exec -u root jenkins chmod 666 /var/run/docker.sock
- Verify Docker works from inside the Jenkins container by running:
docker exec jenkins docker version
You should see both a Client and Server section in the output. This confirms Jenkins can talk to Docker.
Seeing permission denied?
- Make sure you ran the chmod command as root (with -u root). The Jenkins user inside the container doesn't have root privileges by default.
- On some systems, the socket path might differ. Run docker exec -u root jenkins ls -la /var/run/docker.sock to confirm the file exists.
Help me fix Docker socket permissions in my Jenkins container.
Complete the Jenkins initial setup wizard
Jenkins requires a one-time setup to unlock the server and install its core plugins. You'll need the auto-generated admin password that Jenkins created when it started.
- Retrieve the initial admin password by running:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
- Copy the long alphanumeric string that appears in your terminal.
- Open your web browser and navigate to http://localhost:8080.
- Paste the admin password into the Administrator password field.
- Click Continue.
Jenkins now asks you to choose how to install plugins.
- Click Install suggested plugins.
Why install suggested plugins?
The suggested plugins bundle covers the most common use cases including Git integration, Pipeline support, and security features. You'll add the Docker Pipeline plugin separately in the next substep.
Wait for the plugin installation to complete. This may take a few minutes depending on your internet connection.
- When the Create First Admin User screen appears, click Skip and continue as admin.
- On the Instance Configuration screen, leave the URL as http://localhost:8080/ and click Save and Finish.
- Click Start using Jenkins.
You should now see the Jenkins dashboard with a welcome message.
Page not loading at localhost:8080?
- Jenkins takes 30-60 seconds to fully start. Wait a moment and refresh the page.
- Run docker logs jenkins to check if Jenkins is still initializing. Look for the line containing the admin password as a sign that startup is complete.
Help me troubleshoot why Jenkins isn't loading in my browser.
Install the Docker Pipeline plugin
The Docker Pipeline plugin enables Docker-related steps in Jenkins Pipeline jobs. You need it so your pipeline can integrate with Docker builds.
- In the Jenkins dashboard, click Manage Jenkins in the left sidebar.
- Click Plugins.
- Click the Available plugins tab.
- Type Docker Pipeline in the search box.
- Check the box next to Docker Pipeline.
- Click Install (or Install without restart).
Wait for the installation to complete. You should see a success checkmark next to the plugin name.
What is the Docker Pipeline plugin?
This plugin allows building, testing, and using Docker images from Jenkins Pipeline projects. While your Jenkinsfile will use shell commands for Docker operations, the plugin provides the underlying integration framework that makes Docker-aware pipelines possible.
- Click the Jenkins logo in the top-left corner to return to the dashboard.
Can't find the Docker Pipeline plugin?
- Make sure you're on the Available plugins tab, not the Installed plugins tab.
- If the available plugins list is empty, your Jenkins instance may not have fetched the plugin metadata yet. Click Check now at the bottom of the page to refresh the list from the Update Center.
Help me find and install the Docker Pipeline plugin in Jenkins.
Jenkins is up, configured, and ready to build Docker images. Next up, you'll write Terraform configuration to define your Kubernetes infrastructure as code.
Write Terraform Configuration for Kubernetes
Your Jenkins server is running and ready to automate builds. Now you need to tell Kubernetes what to deploy and how to expose it.
Instead of running kubectl commands manually, you will use Terraform to define your Infrastructure as Code. This means your entire deployment configuration lives in a version-controlled file that anyone can read, review, and reproduce.
In this step, get ready to:
- Create Terraform configuration files using the hashicorp/kubernetes provider.
- Define a Kubernetes namespace, deployment, and service as code.
- Apply the configuration to provision resources on your Minikube cluster.
Create the Terraform directory and configure the provider
Terraform reads .tf files from a working directory. You need a dedicated folder for your infrastructure code and a configuration that tells Terraform which provider to use and how to connect to your cluster.
- Create a terraform directory inside your project folder and move into it by running these commands:
mkdir terraform
cd terraform
- Create a file called main.tf inside the terraform directory and add the following provider configuration and namespace resource:
# Tell Terraform which provider to download and use
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 3.1"
}
}
required_version = ">= 1.0.0"
}
# Connect to your local Minikube cluster via kubeconfig
provider "kubernetes" {
config_path = "~/.kube/config"
}
# Create a dedicated namespace for your pipeline resources
resource "kubernetes_namespace" "pipeline" {
metadata {
name = "devops-pipeline"
}
}
What does this code do?
The terraform block tells Terraform to download the hashicorp/kubernetes provider at version ~> 3.1. This provider gives Terraform the ability to create Kubernetes resources.
The provider block points Terraform at your Minikube cluster by reading the kubeconfig file at ~/.kube/config. This is the same config file that kubectl uses.
The kubernetes_namespace resource creates a namespace called devops-pipeline to keep your application's pods and services isolated from other workloads on the cluster.
- Save main.tf.
- You won't see a visible change yet. The next section adds the deployment and service resources that use this namespace.
Not sure where to create the file?
Make sure you are inside the terraform subdirectory of your project. If you are using VS Code, right-click the terraform folder in the Explorer sidebar and select New File, then name it main.tf.
help me create the Terraform directory and file in my project
Define the Kubernetes deployment and service
With the provider and namespace in place, you can now define the resources that actually run your application. A Deployment tells Kubernetes how many copies of your container to run. A Service exposes those containers so they can receive traffic.
- In main.tf, add the following deployment resource below the namespace resource:
# Define the application deployment with 2 replicas
resource "kubernetes_deployment" "app" {
metadata {
name = "pipeline-app"
namespace = kubernetes_namespace.pipeline.metadata[0].name
labels = {
app = "pipeline-app"
}
}
spec {
replicas = 2
selector {
match_labels = {
app = "pipeline-app"
}
}
template {
metadata {
labels = {
app = "pipeline-app"
}
}
spec {
container {
image = "pipeline-app:v1"
name = "pipeline-app"
port {
container_port = 3000
}
}
}
}
}
}
What does this deployment do?
- replicas = 2 tells Kubernetes to run 2 copies of your container for redundancy.
- The selector and template labels must match so Kubernetes knows which pods belong to this deployment.
- image = "pipeline-app:v1" references the Docker image you built in Step 2.
- container_port = 3000 exposes the port your Node.js server listens on.
- Save main.tf.
- You won't see a change yet. One more resource defines how external traffic reaches these pods.
- Still in main.tf, add the following service resource below the deployment:
# Expose the app via a NodePort service on port 30100
resource "kubernetes_service" "app" {
metadata {
name = "pipeline-app-service"
namespace = kubernetes_namespace.pipeline.metadata[0].name
}
spec {
selector = {
app = "pipeline-app"
}
type = "NodePort"
port {
port = 3000
target_port = 3000
node_port = 30100
}
}
}
What does this service do?
A NodePort service exposes your application on a static port on every node in the cluster. Port 30100 is the port you will use to access your app from outside the cluster.
The selector matches the app = "pipeline-app" label on your deployment's pods. This is how the service knows which pods to route traffic to.
- Save main.tf.
Why Terraform instead of kubectl apply?
You could deploy to Kubernetes with kubectl apply -f and YAML manifests. Terraform adds state management on top. It tracks what it created, detects drift, and can destroy everything cleanly with one command.
In a pipeline context, Terraform also lets you manage cloud resources (databases, load balancers, DNS) alongside Kubernetes resources in the same workflow. That single-tool consistency is why production teams choose it.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf with all three resources (namespace, deployment, service) before moving on.
ⓧ I'd like to double check the full code
Here is the complete terraform/main.tf file. Make sure yours matches exactly:
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 3.1"
}
}
required_version = ">= 1.0.0"
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_namespace" "pipeline" {
metadata {
name = "devops-pipeline"
}
}
resource "kubernetes_deployment" "app" {
metadata {
name = "pipeline-app"
namespace = kubernetes_namespace.pipeline.metadata[0].name
labels = {
app = "pipeline-app"
}
}
spec {
replicas = 2
selector {
match_labels = {
app = "pipeline-app"
}
}
template {
metadata {
labels = {
app = "pipeline-app"
}
}
spec {
container {
image = "pipeline-app:v1"
name = "pipeline-app"
port {
container_port = 3000
}
}
}
}
}
}
resource "kubernetes_service" "app" {
metadata {
name = "pipeline-app-service"
namespace = kubernetes_namespace.pipeline.metadata[0].name
}
spec {
selector = {
app = "pipeline-app"
}
type = "NodePort"
port {
port = 3000
target_port = 3000
node_port = 30100
}
}
}
Initialize and apply Terraform
Your configuration file is ready. Before Terraform can create resources, it needs to download the Kubernetes provider plugin. Then you can apply the configuration to provision everything on your Minikube cluster.
- First, load your Docker image into Minikube so the cluster can access it. Run this command from your project root (not from inside the terraform directory):
minikube image load pipeline-app:v1
Why load the image into Minikube?
Minikube runs its own container runtime separate from your host Docker. Even though you built pipeline-app:v1 locally, Minikube cannot see it until you explicitly transfer it with minikube image load. Without this step, the deployment would fail with an image pull error.
- Move into the terraform directory and initialize Terraform by running:
cd terraform
terraform init
- You should see output ending with the message:
Terraform has been successfully initialized!
Seeing a provider error?
If you see an error about the provider not being found, check that your required_providers block matches the code above exactly. The source must be hashicorp/kubernetes with version ~> 3.1.
help me fix a Terraform init provider error for the hashicorp/kubernetes provider
- Apply the Terraform configuration to create your Kubernetes resources by running:
terraform apply
What happens when you run terraform apply?
Terraform will show you a plan of what it intends to create (3 resources: namespace, deployment, service). It then asks for confirmation before making any changes.
When you see the prompt asking if you want to perform these actions, type yes and press Enter to approve.
- Type yes when Terraform prompts you to confirm.
- You should see output ending with a message like:
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
- Verify that your pods are running by running:
kubectl get pods -n devops-pipeline
- You should see 2 pods with names starting with pipeline-app and a status of Running.
Pods stuck in ImagePullBackOff or ErrImageNeverPull?
This means Minikube cannot find the pipeline-app:v1 image. Make sure you ran minikube image load pipeline-app:v1 before applying Terraform. You can check loaded images with:
minikube image list
If the image is missing, run the load command again, then run kubectl rollout restart deployment/pipeline-app -n devops-pipeline to restart the pods.
help me fix pods stuck in ImagePullBackOff on Minikube
Your Kubernetes infrastructure is now defined as code and running on Minikube. Next up, you will wire everything together with a Jenkins pipeline that builds your Docker image and runs Terraform automatically.
Create the Jenkins Pipeline
Your Kubernetes infrastructure is provisioned and ready. Now it is time to automate the entire workflow so a single click builds your Docker image and deploys it to your cluster.
In this step, you will write a Jenkinsfile that defines your pipeline as code. Then you will create a Pipeline job in Jenkins and trigger it to see all four tools working together end to end.
In this step, get ready to:
- Write a Jenkinsfile with Declarative Pipeline stages.
- Create a Pipeline job in Jenkins that runs the Jenkinsfile.
- Trigger the pipeline and verify the application is deployed and accessible via Minikube.
Write a Jenkinsfile with pipeline stages
A Jenkinsfile is a text file that lives at the root of your project and defines your entire CI/CD pipeline as code. Declarative Pipeline syntax makes it readable even if you have never written Groovy before.
- Create a file called Jenkinsfile (no extension) in the root of your devops-pipeline project folder by running:
touch Jenkinsfile
- Open Jenkinsfile in VS Code and paste the following pipeline definition:
pipeline {
agent any
environment {
IMAGE_NAME = 'pipeline-app'
IMAGE_TAG = 'v1'
}
stages {
stage('Build Docker Image') {
steps {
sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ./app"
}
}
stage('Load Image to Minikube') {
steps {
sh "minikube image load ${IMAGE_NAME}:${IMAGE_TAG}"
}
}
stage('Deploy with Terraform') {
steps {
dir('terraform') {
sh 'terraform init'
sh 'terraform apply -auto-approve'
}
}
}
stage('Verify Deployment') {
steps {
sh 'kubectl get pods -n devops-pipeline'
sh 'kubectl get svc -n devops-pipeline'
}
}
}
}
What does this pipeline do?
- environment defines variables shared across all stages. Changing IMAGE_TAG in one place updates every command that references it.
- Build Docker Image builds your container from the app/ directory.
- Load Image to Minikube transfers the locally built image into Minikube's container runtime so Kubernetes can use it without a remote registry.
- Deploy with Terraform runs terraform init and terraform apply -auto-approve inside the terraform/ directory.
- Verify Deployment prints running pods and services so you can confirm everything is healthy.
Why Declarative Pipeline over Scripted Pipeline?
Declarative Pipeline uses a structured, opinionated syntax that is easier to read and maintain. It is also what most teams use in production and what interviewers expect you to understand.
- Save Jenkinsfile.
- Confirm the file exists by running:
ls Jenkinsfile
You should see Jenkinsfile listed in the output.
Don't see the file?
Make sure you created the file in your project root (the same folder that contains app/ and terraform/). Run ls to check you are in the right directory.
help me find where my Jenkinsfile should go in my project structure
✔️ Awesome, I've got everything!
Great. Double check you have saved the file before moving on.
ⓧ I'd like to double check the full code
pipeline {
agent any
environment {
IMAGE_NAME = 'pipeline-app'
IMAGE_TAG = 'v1'
}
stages {
stage('Build Docker Image') {
steps {
sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ./app"
}
}
stage('Load Image to Minikube') {
steps {
sh "minikube image load ${IMAGE_NAME}:${IMAGE_TAG}"
}
}
stage('Deploy with Terraform') {
steps {
dir('terraform') {
sh 'terraform init'
sh 'terraform apply -auto-approve'
}
}
}
stage('Verify Deployment') {
steps {
sh 'kubectl get pods -n devops-pipeline'
sh 'kubectl get svc -n devops-pipeline'
}
}
}
}
Create a Pipeline job in Jenkins
Now you need to tell Jenkins about your pipeline. You will create a new job in the Jenkins UI and paste your pipeline script into it.
- First, copy your project files into the Jenkins container's workspace so the pipeline can access them. Run:
docker exec jenkins mkdir -p /var/jenkins_home/workspace/devops-pipeline
docker cp app jenkins:/var/jenkins_home/workspace/devops-pipeline/app
docker cp terraform jenkins:/var/jenkins_home/workspace/devops-pipeline/terraform
Why copy files into Jenkins?
Jenkins runs inside a Docker container with its own filesystem. The pipeline stages reference ./app and terraform/ relative to the job workspace. Copying them into the container ensures the pipeline can find them when it runs.
- Open your browser and return to Jenkins at http://localhost:8080.
- Click New Item in the top left of the Jenkins Dashboard.
- In the Enter an item name field, type devops-pipeline.
- Scroll down and select Pipeline.
- Click OK at the bottom of the page.
This opens the Pipeline configuration page.
- Scroll down to the Pipeline section at the bottom of the configuration page.
- Make sure the Definition dropdown says Pipeline script.
- Paste the entire contents of your Jenkinsfile into the Script text area.
- Click Save.
You should now see the devops-pipeline job page in Jenkins with a Build Now option in the left sidebar.
Don't see the Pipeline section?
Click the Pipeline tab in the left sidebar of the configuration page. This scrolls directly to the Pipeline section.
help me find the Pipeline script configuration in Jenkins
Trigger the pipeline and verify deployment
Everything is wired up. Time to run the pipeline and watch Jenkins orchestrate your entire build-deploy workflow.
- Click Build Now in the left sidebar of the devops-pipeline job page.
- Watch the build progress in the Build History section on the left. Click on the build number (#1) to see details.
- Click Console Output in the left sidebar to see live logs of each stage.
What should I see?
The Console Output shows each stage executing in sequence. You should see Docker building the image, Minikube loading it, Terraform initializing and applying, and finally kubectl printing your pods and services. The build finishes with Finished: SUCCESS.
- Verify the application is accessible on your Minikube cluster by running:
minikube service pipeline-app-service -n devops-pipeline --url
This prints a URL. Open it in your browser or use curl to confirm you see the JSON response from your app.
- Test the response by running:
curl $(minikube service pipeline-app-service -n devops-pipeline --url)
You should see a JSON object with "message": "Hello from the DevOps Pipeline!" and "version": "v1".
Pipeline failed at a stage?
Check the Console Output for the exact error message. Common causes:
- Build Docker Image fails: the app/ directory was not copied into the Jenkins workspace. Re-run the docker cp commands from earlier.
- Deploy with Terraform fails: the terraform/ directory is missing from the workspace, or terraform is not on the PATH inside the Jenkins container.
- Verify Deployment fails: kubectl cannot reach the Minikube cluster. Ensure ~/.kube/config is accessible from inside the Jenkins container.
help me debug my Jenkins pipeline failure
Your full CI/CD pipeline is running. Jenkins just built, loaded, deployed, and verified your application in a single click. Next, you will take this further in the Secret Mission by implementing zero-downtime rolling updates.
Secret mission
Add Rolling Updates
Your pipeline deploys new versions, but what happens to users during the switch? Right now, Kubernetes replaces all pods at once, causing a brief outage. In this mission, you'll configure a rolling update strategy so new versions go live with zero downtime.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally on your machine, so there are no ongoing cloud costs. However, the Jenkins container and Minikube cluster consume RAM while running.
Resources you used:
- Jenkins container (running on port 8080, consuming ~512MB RAM).
- Minikube cluster (running as a Docker container, consuming ~2GB RAM).
- Docker images (pipeline-app:v1 and pipeline-app:v2, taking disk space).
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your pipeline.
- Jenkins stays available at http://localhost:8080.
- Your Minikube cluster keeps running with the deployed application.
- You can trigger new pipeline builds or modify your Terraform configuration at any time.
Pause - I'll come back to this later
Shut down running processes to free up RAM while preserving your state. You can restart everything later without losing progress.
- Stop the Minikube cluster by running:
minikube stop
- Stop the Jenkins container by running:
docker stop jenkins
- To resume later, run minikube start and docker start jenkins.
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees up all RAM and disk space used by the project.
- Delete the Minikube cluster (this also removes all Kubernetes resources inside it) by running:
minikube delete
- Remove the Jenkins container by running:
docker rm -f jenkins
- Remove the Docker images by running:
docker rmi pipeline-app:v1 pipeline-app:v2
- Delete your local project files. Navigate to your Desktop and remove the project folder by running:
cd ~/Desktop
ls
- Find the folder that contains your app/, terraform/, and Jenkinsfile, then remove it:
rm -rf [[PROJECT_FOLDER="your project folder name"]]
That's a Wrap!
That's a Wrap!
Nice work! You just built a production-style CI/CD pipeline entirely on your local machine.
You've learned how to:
- Built a Docker image to containerize a Node.js application and ran Jenkins as a Docker container to automate builds.
- Created a Declarative Pipeline in Jenkins with stages for building, loading, deploying, and verifying your application.
- Wrote Terraform Infrastructure as Code to provision Kubernetes resources and deployed them to a local Minikube cluster via an automated pipeline.
- Implemented rolling updates for zero-downtime deployments by configuring a RollingUpdate strategy in your Terraform deployment.
Ready to quiz yourself?