Jenkins CI/CD Pipeline with Load Testing

Build a Jenkins pipeline in Docker that builds, tests, and load tests a Java app.

Introduction

30 Second Summary

Every time a developer pushes code, someone needs to make sure it actually works, runs fast enough, and doesn't break anything else. In the real world, that "someone" is usually an automated pipeline that builds, tests, and delivers software without any human clicking buttons.

In this project, you will set up a Jenkins CI/CD pipeline inside a Docker container that automatically builds a Java application, run load tests against it using k6, and investigate build logs to diagnose a failure.

What You'll Build

A complete DevOps integration environment on your local machine that combines CI/CD automation, performance testing, and incident investigation.

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

  • A Jenkins server running in a Docker container with a Declarative Pipeline that builds and tests a Java application using Maven.
  • A k6 load test script that stress-tests your application and reports performance metrics like response times, throughput, and error rates.
  • A structured incident report documenting a build failure you diagnosed through Jenkins log analysis.
  • Secret Mission: Add an automated performance gate to your pipeline that fails builds when response times exceed your threshold.

Are there any prerequisites?

This project assumes working knowledge of Linux and Git. Basic familiarity with Docker, Jenkins, and Java concepts is helpful, but the project provides hands-on guidance through each tool.

Before We Start

In this project, you will build a complete DevOps integration environment on your local machine. You will set up a Jenkins CI/CD pipeline that builds a Java application, run load tests against it, and investigate logs to diagnose failures.

Before diving into the pipeline, you need a few tools installed on your Windows machine. This step gets your environment ready with Docker, a load testing tool called k6, a code editor, and Git.

In this step, get ready to:
  • Install and verify Docker Desktop in Linux container mode.
  • Install k6 for load testing.
  • Confirm VS Code and Git are ready to use.
Install and verify Docker Desktop

Docker Desktop lets you run containers on your Windows machine. Jenkins will run inside a Linux container, so Docker must be configured for Linux containers.

  • Check if Docker is already installed by opening a terminal and running this command:
docker --version

✔️ I see a version number

Docker is installed. Now confirm it is set to Linux containers.

  • Look at the Docker Desktop icon in your system tray (bottom-right of your taskbar).
  • Right-click the Docker icon.
  • If you see the option Switch to Windows containers... in the menu, you are already on Linux containers. No action needed.
  • If you see Switch to Linux containers... instead, click it and wait for Docker to restart.

Why Linux containers?

The Jenkins Docker image is Linux-based. Docker Desktop on Windows must be configured for Linux containers (the default WSL 2 backend) to run it.

This is also how most production containers operate, so you are practicing real-world configuration.

ⓧ Command not found

  • Open your browser and navigate to the Docker Desktop download page:

https://docs.docker.com/desktop/install/windows-install

  • Click Docker Desktop for Windows - x86_64 to download the installer.
  • Double-click Docker Desktop Installer.exe to run the installer.
  • When prompted to choose an installation mode, select the default (per-user). The WSL 2 backend is selected automatically.
  • Follow the installation wizard and click Close when finished.
  • Press the Windows key, type Docker Desktop, and press Enter to launch it.
  • Wait for Docker Desktop to finish starting (the whale icon in your system tray will stop animating).
  • Open a new terminal and verify the installation by running:
docker --version

You should see a version number printed in the terminal.

Why Linux containers?

The Jenkins Docker image is Linux-based. Docker Desktop on Windows must be configured for Linux containers to run it. The installer defaults to the WSL 2 backend, which uses Linux containers automatically.

Docker won't start?

Docker Desktop requires WSL 2 on Windows. If you see an error about WSL, open PowerShell as Administrator and run wsl --install. Restart your machine afterwards, then try launching Docker Desktop again.

help me troubleshoot Docker Desktop not starting on Windows

Install k6 for load testing

k6 is an open-source load testing tool from Grafana. You will use it later to stress-test your Java application and measure performance metrics like response time and throughput.

  • Install k6 using the Windows Package Manager by running this command:
winget install k6 --source winget

What does this command do?

winget is the Windows Package Manager. This command downloads and installs the k6 binary from the official winget source so you can run load tests from your terminal.

  • Verify k6 installed correctly by running:
k6 version

✔️ I see version information

k6 is installed and ready. You will write a load test script with it in a later step.

ⓧ Command not found

Your terminal may not have refreshed its PATH after installation.

  • Close your current terminal window completely.
  • Open a new terminal window.
  • Run k6 version again.

If it still fails, try installing with Chocolatey instead:

choco install k6

Still not working?

help me install k6 on Windows when winget and choco both fail

Confirm VS Code and Git are ready

You need a code editor to write your pipeline configuration and load test scripts. You also need Git to clone the sample repository and push changes.

  • Check that Git is installed by running:
git --version

✔️ I see a version number

Git is ready. Move on to checking VS Code below.

ⓧ Command not found

  • Download Git for Windows from:

https://git-scm.com/downloads/win

  • Run the installer and accept the default settings for each prompt.
  • Close and reopen your terminal, then run git --version again to confirm.
  • Open VS Code by pressing the Windows key, typing VS Code, and pressing Enter.

✔️ VS Code opens

You are all set. Keep VS Code open for the next step.

ⓧ VS Code is not installed

  • Download VS Code from:

https://code.visualstudio.com/

  • Run the installer and accept the defaults.
  • Launch VS Code after installation completes.

Your environment is ready. Next up, you will set up a Jenkins server inside a Docker container and create your first pipeline.

Set Up Docker and Jenkins

Jenkins is the CI/CD server that will automatically build and test your Java application. Instead of installing Jenkins directly on your machine, you will run it inside a Docker container.

Running Jenkins in Docker gives you a clean, isolated Linux environment that matches how CI/CD servers run in production. In this step, you will start a Jenkins container and complete its Setup Wizard.

In this step, get ready to:
  • Verify Docker Desktop is running in Linux container mode.
  • Pull and start the Jenkins Docker container with persistent storage.
  • Complete the Jenkins Setup Wizard and access the dashboard.
Verify Docker Desktop is configured for Linux containers

The Jenkins Docker image is Linux-based. Docker Desktop must be running in Linux container mode for the container to start properly.

  • Switch back to Docker Desktop on your taskbar.
  • Right-click the Docker icon in your system tray (bottom-right corner of your screen).
  • Confirm you see Switch to Windows containers... in the menu. This means Docker is currently using Linux containers, which is what you need.

See "Switch to Linux containers" instead?

If the menu says Switch to Linux containers..., that means Docker is currently in Windows container mode. Click it to switch to Linux containers, then wait for Docker to restart.

Help me switch Docker Desktop to Linux containers.

Pull and start the Jenkins Docker container

You will start a Jenkins container with two Docker volumes. These volumes store Jenkins data and certificates outside the container, so your configuration survives restarts.

  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Start the Jenkins container by running this command:
docker run --name jenkins --detach --publish 8080:8080 --publish 50000:50000 --volume jenkins-data:/var/jenkins_home --volume jenkins-docker-certs:/certs/client jenkins/jenkins:lts

What does this command do?

  • --name jenkins gives the container a friendly name you can reference later.
  • --detach runs the container in the background so you keep your terminal free.
  • --publish 8080:8080 maps port 8080 on your machine to port 8080 in the container. This is how you access the Jenkins web interface.
  • --publish 50000:50000 opens the port Jenkins uses to communicate with build agents.
  • --volume jenkins-data:/var/jenkins_home persists all Jenkins jobs, plugins, and configuration to a named Docker volume.
  • --volume jenkins-docker-certs:/certs/client stores Docker TLS certificates for secure communication.
  • jenkins/jenkins:lts is the official Jenkins Long Term Support image, which comes with Java pre-installed.
  • Verify the container is running by running:
docker ps

You should see a row with jenkins/jenkins:lts in the IMAGE column and Up in the STATUS column.

Don't see the container running?

  • If you see an error about the name already being in use, a previous container with the same name exists. Remove it by running docker rm jenkins and then re-run the start command.
  • If you see a port conflict error, another application is using port 8080. Close that application and try again.
  • Make sure Docker Desktop is running and in Linux container mode before executing the command.

Help me troubleshoot my Jenkins Docker container not starting.

Complete the Jenkins Setup Wizard

The first time Jenkins starts, it generates a one-time password you need to unlock the web interface. You will retrieve this password from inside the container.

  • Retrieve the initial admin password by running this command:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

What does this command do?

docker exec jenkins runs a command inside the running Jenkins container. cat /var/jenkins_home/secrets/initialAdminPassword reads the auto-generated password file that Jenkins created on first boot.

You should see a long string of letters and numbers printed in your terminal. Copy this password.

Seeing "Error: No such container"?

This means the container is not running. Run docker ps -a to check its status. If the status shows Exited, start it again with docker start jenkins and wait a few seconds before retrying.

Help me fix the "No such container" error for Jenkins.

  • Open your web browser and navigate to http://localhost:8080.

Give Jenkins a moment

Jenkins can take 30-60 seconds to fully start. If you see a "site can't be reached" page, wait a moment and refresh.

  • On the Unlock Jenkins page, paste the password you copied into the Administrator password field.
  • Click Continue.
  • On the Customize Jenkins page, click Install suggested plugins.

Jenkins will download and install its most commonly used plugins. This takes a few minutes.

  • Once the plugins finish installing, you will see the Create First Admin User page.
  • Fill in a username, password, full name, and email address of your choice.
  • Click Save and Finish.
  • On the next page, click Start using Jenkins.

You should now see the Jenkins dashboard with a Welcome to Jenkins! message.

Why persistent volumes?

Without the jenkins-data volume, all your plugins, jobs, and settings would disappear every time the container restarts. Named volumes store this data on your host machine so Jenkins picks up exactly where it left off.

Your Jenkins server is live and ready to run pipelines. Next up, you will fork a Java application and create a Jenkinsfile that tells Jenkins exactly how to build, test, and deliver it.

Fork, Clone, and Configure the Java Pipeline

Your Jenkins server is up and running, but it has nothing to build yet. A CI/CD pipeline needs source code to work with, and that code lives in a Git repository.

In this step, you will fork a sample Java application, define a Jenkinsfile that describes your pipeline stages, and connect it all to Jenkins so it can automatically build, test, and deliver the app.

In this step, get ready to:
  • Fork and clone the sample Java repository from GitHub.
  • Create a Jenkinsfile with Build, Test, and Deliver stages.
  • Configure and run a Pipeline job in Jenkins.
Fork and clone the sample repository

Jenkins provides an official sample app called simple-java-maven-app that prints "Hello world!" and includes unit tests. You will fork this repository so you have your own copy to push changes to.

Why fork instead of clone directly?

Forking creates your own copy of the repository under your GitHub account. Jenkins needs to pull the code from a repository you control, so you can push changes (like the Jenkinsfile) and Jenkins can detect them.

  • Once the fork is complete, copy the HTTPS URL of your forked repository from the Code dropdown button on your fork's page.
  • Open your terminal.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone your forked repository by running this command:
git clone [[YOUR_REPO_URL="https://github.com/YOUR-USERNAME/simple-java-maven-app.git"]]
  • Move into the cloned project folder by running:
cd simple-java-maven-app
  • Confirm you are in the correct directory by running:
ls

You should see files including pom.xml, a src folder, and a jenkins folder in the output.

Don't see those files?

  • Make sure you ran cd simple-java-maven-app after cloning. You need to be inside the project folder.
  • If the clone failed, double-check that you pasted the correct HTTPS URL from your fork (not the original jenkins-docs repository).

Help me troubleshoot my git clone

Create the Jenkinsfile

A Jenkinsfile is a text file that defines your pipeline stages using Declarative Pipeline syntax. Jenkins reads this file from your repository and executes each stage in order.

  • Create a new file called Jenkinsfile in the root of your simple-java-maven-app folder by running:
touch Jenkinsfile
  • Open Jenkinsfile in your text editor and paste the following pipeline definition:
pipeline {
    agent any
    options {
        skipStagesAfterUnstable()
    }
    stages {
        stage('Build') {
            steps {
                // Compile the Java app with Maven, skipping tests for speed
                sh 'mvn -B -DskipTests clean package'
            }
        }
        stage('Test') {
            steps {
                // Run the unit tests
                sh 'mvn test'
            }
            post {
                always {
                    // Publish JUnit test results regardless of pass or fail
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }
        stage('Deliver') {
            steps {
                // Run the delivery script included in the repository
                sh './jenkins/scripts/deliver.sh'
            }
        }
    }
}

What does this pipeline do?

  • agent any tells Jenkins to run the pipeline on any available executor.
  • skipStagesAfterUnstable() prevents later stages from running if a test stage marks the build as unstable.
  • The Build stage compiles the Java application using Maven without running tests.
  • The Test stage runs unit tests and publishes JUnit XML reports so Jenkins can display results.
  • The Deliver stage runs a shell script that briefly starts the application to confirm it works.
  • Save the Jenkinsfile.
  • Stage, commit, and push the new file to your fork by running these commands:
git add Jenkinsfile
git commit -m "Add Jenkinsfile with Build, Test, and Deliver stages"
git push origin master

Why push to master?

The simple-java-maven-app repository uses master as its default branch. When you forked it, your fork inherited the same branch name. Jenkins will look for the Jenkinsfile on this branch.

Push rejected or authentication error?

  • If Git asks for credentials, use a GitHub Personal Access Token as your password (GitHub no longer accepts account passwords for HTTPS).
  • Make sure you cloned your fork (your username in the URL), not the original jenkins-docs repository.

Help me fix my git push error

\u2714\uFE0F Awesome, I've got everything!

Great. Double check you have saved your file and pushed it successfully.

\u24E7 I'd like to double check the full code

Here is the complete Jenkinsfile that should be in the root of your repository:

pipeline {
    agent any
    options {
        skipStagesAfterUnstable()
    }
    stages {
        stage('Build') {
            steps {
                sh 'mvn -B -DskipTests clean package'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }
        stage('Deliver') {
            steps {
                sh './jenkins/scripts/deliver.sh'
            }
        }
    }
}
Configure and run the Pipeline job in Jenkins

Now that your Jenkinsfile is in your repository, you need to tell Jenkins where to find it. You will create a Pipeline job that pulls code from your fork and runs the stages you defined.

  • Switch back to your browser and open the Jenkins dashboard at http://localhost:8080.
  • Click New Item in the top left of the dashboard.
  • In the Enter an item name field, type simple-java-maven-app.
  • Select Pipeline from the list of project types.
  • Click OK at the bottom of the page.

The Pipeline configuration page opens.

  • Scroll down to the Pipeline section at the bottom of the configuration page.
  • Change the Definition dropdown from "Pipeline script" to Pipeline script from SCM.
  • In the SCM dropdown, select Git.
  • In the Repository URL field, paste the HTTPS URL of your forked repository (the same URL you used to clone).
  • In the Branch Specifier field, make sure it reads */master.
  • Leave the Script Path as Jenkinsfile (the default).
  • Click Save.

What is Pipeline script from SCM?

This setting tells Jenkins to look for a file called Jenkinsfile in your Git repository instead of expecting you to type the pipeline script directly into the Jenkins UI. This is the "Pipeline as Code" approach where your pipeline definition lives alongside your source code.

  • On the project page that appears, click Build Now in the left sidebar to trigger your first pipeline run.
  • Wait for the build to complete. Click the build number (e.g. #1) in the Build History panel on the left to see details.
  • Click Console Output in the left sidebar to see the full log of your pipeline run.

You should see all three stages (Build, Test, Deliver) complete with a Finished: SUCCESS message at the bottom of the console output.

What should I see?

The first build may take a few minutes as Maven downloads dependencies. In the console log, you will see Maven compiling Java source files, running unit tests, and then the deliver script starting and stopping the application.

The Stage View on the project page shows three green boxes labeled Build, Test, and Deliver, each with a checkmark and an execution time.

Build failed or stuck?

  • If you see a Git error in the console output, double-check the Repository URL in your Pipeline configuration matches your fork's HTTPS URL exactly.
  • If the Build stage fails with a Maven error, make sure you did not accidentally modify any files in the src directory. The Jenkinsfile should be the only new file.
  • If the build is stuck with no output, your Jenkins Docker container may need more memory. Restart Docker Desktop and try again.

Help me debug my Jenkins pipeline failure

Your pipeline is building, testing, and delivering a Java application automatically. Next up, you will write load tests with k6 to measure how your application performs under pressure.

Run Load Tests with k6

Your Jenkins pipeline builds, tests, and delivers the Java application automatically. But how does the app actually perform under real-world traffic?

In this step, you will install k6, an open-source load testing tool from Grafana. You will write a test script that simulates multiple virtual users hitting your application endpoint, then analyze the results to understand response times, throughput, and error rates.

In this step, get ready to:
  • Install k6 on your machine and verify it is available.
  • Write a k6 load test script with ramping virtual users and performance thresholds.
  • Execute the load test and analyze the performance results.
Install k6

k6 is a developer-friendly load testing tool. You write test scripts in JavaScript, and k6 simulates virtual users making HTTP requests against your target endpoint.

  • Check if k6 is already installed by running this command in your terminal:
k6 version

✔️ I see version information

k6 is already installed and ready to use. Skip ahead to the next substep.

ⓧ Command not found

  • Install k6 using the Windows Package Manager by running:
winget install k6 --source winget

What does this command do?

This uses winget, the Windows Package Manager, to download and install k6 from the official community manifests. The --source winget flag ensures it pulls from the winget repository specifically.

  • After installation completes, close and reopen your terminal.
  • Verify the installation by running:
k6 version

You should see version information displayed in your terminal, confirming k6 is installed.

Still seeing command not found?

Make sure you closed and reopened your terminal after the install. The winget installer adds k6 to your PATH, but existing terminal sessions do not pick up PATH changes automatically.

If the issue persists, try downloading the official Windows installer from the k6 GitHub Releases page instead.

Help me troubleshoot my k6 installation on Windows.

Write the k6 load test script

A k6 test script defines how many virtual users hit your endpoint, how long the test runs, and what counts as acceptable performance. You will target the Jenkins server running at http://localhost:8080.

  • In your simple-java-maven-app project folder, create a new file called load-test.js by running:
touch load-test.js
  • Open load-test.js in VS Code and paste the following code:
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
    stages: [
        { duration: '10s', target: 10 },
        { duration: '20s', target: 10 },
        { duration: '10s', target: 0 },
    ],
    thresholds: {
        http_req_duration: ['p(95)<500'],
        http_req_failed: ['rate<0.01'],
    },
};

export default function () {
    const res = http.get('http://localhost:8080');
    check(res, {
        'status is 200': (r) => r.status === 200,
    });
    sleep(1);
}

What does this code do?

  • stages defines the traffic pattern: ramp up to 10 virtual users over 10 seconds, hold at 10 for 20 seconds, then ramp back down to 0 over 10 seconds.
  • thresholds sets pass/fail criteria. If the 95th percentile response time exceeds 500ms or more than 1% of requests fail, k6 marks the test as failed.
  • The default function runs once per virtual user per iteration. It sends a GET request to http://localhost:8080, checks for a 200 status, then pauses 1 second before the next iteration.
  • Save load-test.js.

Seeing a syntax error when you save?

Make sure the file extension is .js and not .ts. k6 scripts use JavaScript module syntax with import and export keywords.

Help me fix a syntax error in my k6 load test script.

✔️ Awesome, I've got everything!

Great. Double check you have saved load-test.js in your project root.

ⓧ I'd like to double check the full code

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
    stages: [
        { duration: '10s', target: 10 },
        { duration: '20s', target: 10 },
        { duration: '10s', target: 0 },
    ],
    thresholds: {
        http_req_duration: ['p(95)<500'],
        http_req_failed: ['rate<0.01'],
    },
};

export default function () {
    const res = http.get('http://localhost:8080');
    check(res, {
        'status is 200': (r) => r.status === 200,
    });
    sleep(1);
}
Execute the load test and analyze results

With your Jenkins server still running at http://localhost:8080, you can now point k6 at it. The load test simulates 10 concurrent users accessing the Jenkins landing page.

  • Run the load test by executing this command from your simple-java-maven-app folder:
k6 run load-test.js

What should I see?

k6 prints a real-time progress bar as the test runs (about 40 seconds total). Once complete, it displays a summary table with key metrics:

  • http_req_duration shows average, min, median, max, p90, and p95 response times.
  • http_req_failed shows the percentage of requests that returned an error.
  • http_reqs shows total requests made and requests per second (throughput).
  • The thresholds section shows a green checkmark if your criteria passed, or a red cross if they failed.
  • Review the summary output in your terminal. Note the p95 response time, the total number of requests, and whether your thresholds passed or failed.

Threshold failures are not errors

If the Jenkins login page responds slowly under load, k6 may report a threshold breach. This is expected behavior. The threshold tells you the application did not meet your performance criteria. In a real pipeline, this signal would block a deployment.

Seeing connection refused errors?

Make sure your Jenkins Docker container is still running. Check with docker ps to confirm the container is active and port 8080 is mapped.

If the container stopped, restart it and wait for Jenkins to fully boot before running the load test again.

Help me fix connection refused errors when running k6 against localhost:8080.

You now have concrete performance data for your application. Next up, you will investigate what happens when a pipeline build fails and practice writing an incident report from the logs.

Investigate Logs and Report an Incident

Your pipeline is green. Every stage passes. But in real production environments, builds break all the time.

In this step, you will deliberately introduce a failure into the code, observe how Jenkins surfaces it, and then write a structured incident report. This mirrors how DevOps engineers investigate failing builds and document root causes for their teams.

In this step, get ready to:
  • Introduce a unit test failure and push it to your repository.
  • Observe the pipeline failure in Jenkins console output.
  • Create a structured incident report documenting the failure.
Introduce a unit test failure

To practice log analysis, you need a real failure to investigate. The simplest way to break the build is to change the application's output string so it no longer matches what the unit test expects.

  • In VS Code, open the file src/main/java/com/mycompany/app/App.java in your simple-java-maven-app project.
  • Find the line that defines the MESSAGE constant:
private static final String MESSAGE = "Hello World!";
  • Change the string from "Hello World!" to "Goodbye World!" so the line now looks like this:
private static final String MESSAGE = "Goodbye World!";

Why does this break the build?

The unit test in AppTest.java has an assertion that checks assertEquals("Hello World!", app.getMessage()). By changing the message to "Goodbye World!", the test will fail because the actual output no longer matches the expected value.

  • Save App.java.
  • Stage, commit, and push the change by running these commands in your terminal:
git add src/main/java/com/mycompany/app/App.java
git commit -m "Introduce intentional test failure"
git push origin master

Branch name may differ

If your forked repository uses main instead of master as the default branch, replace master with main in the push command.

Push rejected or authentication failed?

Make sure you cloned your own fork of the repository (not the original jenkins-docs repo). You need write access to push changes.

If you see a credentials error, you may need to set up a GitHub personal access token or SSH key for authentication.

Help me troubleshoot my Git push failure.

Trigger the pipeline and observe the failure

Now that broken code is in your repository, Jenkins needs to pick it up and run the pipeline.

  • Return to your Jenkins dashboard at http://localhost:8080.
  • Click on your simple-java-maven-app pipeline job.
  • Click Build Now in the left sidebar to trigger a new build.
  • Wait for the build to complete. You should see the build turn red, indicating a failure.
  • Click on the failed build number (e.g., #2) in the build history on the left sidebar.
  • Click Console Output in the left sidebar to view the full build log.

Scroll through the console output. You should see the Test stage fail with a message indicating that the unit test assertion did not match.

What should I see in the console output?

Look for lines mentioning BUILD FAILURE and references to the test report. The output will show that testAppMessage failed because the expected value (Hello World!) did not match the actual value (Goodbye World!).

You will also see a reference to JUnit XML reports in target/surefire-reports/. Jenkins uses these reports to display test results in the build dashboard.

Build shows success instead of failure?

Make sure you saved App.java before committing. Verify your push went through by checking the file on GitHub in your browser.

Also confirm that the pipeline job's SCM URL points to your fork (not the original jenkins-docs repo).

Help me figure out why my Jenkins build is not picking up my code change.

Analyze the logs and create an incident report

In real-world DevOps, finding the failure is only half the job. You also need to document it clearly so your team understands what happened, what caused it, and how it was resolved.

  • From the Jenkins console output, note down the following details:
  • In VS Code, create a new file called incident-report.md in the root of your simple-java-maven-app project.
  • Copy the following template into incident-report.md:
# Incident Report

## Summary
- **Date**: [Date of test execution]
- **Severity**: Medium
- **Status**: Resolved

## Description
Build failure detected in Jenkins Pipeline during the Test stage.

## Root Cause
Unit test failure in [test file name] due to [describe the intentional change made].

## Evidence
- Jenkins Build Number: #[number]
- Console Log excerpt showing the failure:
  ```
  [Paste relevant log lines here]
  ```
- Test Report: [number] tests run, [number] failures

## Resolution
Reverted the code change in [file name] and re-triggered the pipeline build.
Build #[number] passed all stages successfully.

## Lessons Learned
- Always verify unit tests pass locally before committing
- Jenkins console logs provide immediate visibility into test failures
- JUnit XML reports in `target/surefire-reports/` contain detailed failure information

What does this template cover?

This follows a standard incident report structure used in production environments. The Summary section gives a quick overview. Root Cause explains what went wrong. Evidence provides concrete proof from the logs.

Resolution documents the fix and confirms recovery. Lessons Learned prevents the same issue from happening again.

  • Fill in the bracketed placeholders with the actual details from your build failure. Replace [test file name] with AppTest.java, describe your change, and paste the relevant log lines from the console output.
  • Save incident-report.md.

Now revert the breaking change so your pipeline passes again.

  • In App.java, change "Goodbye World!" back to "Hello World!":
private static final String MESSAGE = "Hello World!";
  • Save App.java, then commit and push the fix:
git add src/main/java/com/mycompany/app/App.java
git commit -m "Revert test failure - restore Hello World message"
git push origin master
  • Return to Jenkins and click Build Now again to trigger a new build.
  • Wait for it to complete. All stages should pass and the build should turn blue/green.
  • Update the Resolution section of your incident-report.md with the passing build number.
  • Save incident-report.md.

Build still failing after reverting?

Double-check that the MESSAGE string in App.java is exactly "Hello World!" with the exclamation mark. The unit test checks for this exact string including punctuation.

Verify the push went through by refreshing your repository on GitHub. The file should show the original message.

Help me troubleshoot why my Jenkins build is still failing after I reverted my code change.

✔️ Awesome, I've got everything!

Great. Double check you have saved both incident-report.md and App.java (with the original message restored).

ⓧ I'd like to double check the full code

Here is what your incident-report.md template should look like (with your own details filled into the bracketed placeholders):

# Incident Report

## Summary
- **Date**: [Date of test execution]
- **Severity**: Medium
- **Status**: Resolved

## Description
Build failure detected in Jenkins Pipeline during the Test stage.

## Root Cause
Unit test failure in [test file name] due to [describe the intentional change made].

## Evidence
- Jenkins Build Number: #[number]
- Console Log excerpt showing the failure:
  ```
  [Paste relevant log lines here]
  ```
- Test Report: [number] tests run, [number] failures

## Resolution
Reverted the code change in [file name] and re-triggered the pipeline build.
Build #[number] passed all stages successfully.

## Lessons Learned
- Always verify unit tests pass locally before committing
- Jenkins console logs provide immediate visibility into test failures
- JUnit XML reports in `target/surefire-reports/` contain detailed failure information

And here is the App.java file after reverting your change:

package com.mycompany.app;

/**
 * Hello world!
 */
public class App {

    private static final String MESSAGE = "Hello World!";

    public App() {}

    public static void main(String[] args) {
        System.out.println(MESSAGE);
    }

    public String getMessage() {
        return MESSAGE;
    }
}

You have just completed a full incident investigation cycle. You broke a build, analyzed the logs, documented the failure, fixed the issue, and confirmed the resolution. This is exactly how DevOps teams handle production incidents.

Secret mission

Add a Performance Gate to Your Pipeline

Your pipeline builds, tests, and delivers. But what if it also automatically rejected code that slows your application down? In this secret mission, you will add an automated performance gate that runs k6 inside your pipeline and fails the build if response times exceed your thresholds.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally using Docker, 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:

  • Jenkins Docker container (jenkins).
  • Docker volume: jenkins-data (stores Jenkins home directory and job data).
  • Docker volume: jenkins-docker-certs (stores Docker TLS certificates).
  • k6 load testing tool (installed via winget).

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your Jenkins pipeline.

  • Your Jenkins container stays running at http://localhost:8080.
  • k6 remains available on your PATH for future load tests.
  • All pipeline job history, build artifacts, and plugin configurations persist in the jenkins-data volume.

Pause - I'll come back to this later

Stop the Jenkins container to free up system resources. Your data persists in the Docker volumes, so you can restart later without losing anything.

  • Stop the Jenkins container by running this command:
docker stop jenkins
  • When you are ready to resume, start it again by running:
docker start jenkins
  • Visit http://localhost:8080 to confirm Jenkins is back up.

Delete - I don't want to use this again

Remove all project resources and start fresh. This permanently deletes your Jenkins configuration, job history, and build artifacts.

  • Stop and remove the Jenkins container by running:
docker stop jenkins
docker rm jenkins
  • Remove the Docker volumes that stored Jenkins data by running:
docker volume rm jenkins-data jenkins-docker-certs
  • Uninstall k6 from your system by running:
winget uninstall k6
  • Delete your local project folder (the cloned simple-java-maven-app repository) from your Desktop.

Nice Work!

Nice Work!

You just built a professional-grade integration environment from scratch. You now have hands-on experience with the exact workflow DevOps engineers use every day to build, test, and deliver software safely.

Here's what you accomplished:

  • Set up a Jenkins CI/CD server running on Linux via Docker and created a Declarative Pipeline that builds and tests a Java application with Maven.
  • Performed load and performance testing using k6 with thresholds for response times and error rates.
  • Investigated build logs to identify failures and documented a structured incident report.
  • Secret Mission: Added automated performance gates to your pipeline that prevent slow code from being delivered.

Ready to quiz yourself?