🎉 GoReplay is now part of Probe Labs. 🎉

Published on 8/3/2026

Your Practical Jenkins Continuous Integration Tutorial

- A photo-realistic DevOps environment with a subtle server rack and a laptop displaying pipeline code in soft focus, featuring "Jenkins CI Guide" text centered on a solid background block with sharp high-contrast edges, the surrounding imagery subdued to emphasize the clear and legible text

This Jenkins continuous integration tutorial is your practical, hands-on guide to automating software delivery. We’ll go way beyond theory to actually build, test, and deploy applications using the powerful Pipeline-as-Code techniques that are absolutely essential in modern DevOps.

Why Jenkins Is Still Your Go-To for CI/CD

A tech workspace with multiple monitors and a laptop displaying Jenkins Advantage and process flowcharts.

In a pretty crowded field of DevOps tools, Jenkins remains a dominant force for a few very good reasons. Its open-source nature gives you complete control and total freedom from vendor lock-in, which is a massive advantage for any team that needs to customize every last detail of their automation workflows.

This flexibility gets a huge boost from its incredible plugin ecosystem. With thousands of community-built plugins available, you can integrate Jenkins with pretty much any tool, cloud service, or tech stack you can think of. It’s this adaptability that keeps it the automation engine of choice for countless organizations, from nimble startups to massive enterprises.

The Power of a Mature Ecosystem

Jenkins isn’t just a tool; it’s a foundational piece of the entire DevOps movement. Its maturity translates into a stable, well-documented platform with a massive global community. If you run into a problem, chances are someone has already solved it and shared the fix online.

Its long-standing presence has cemented its position in the market. Jenkins commands a massive 44-47% of the global CI/CD market share, with an estimated 11.26 million developers relying on it. This leadership is especially strong in enterprise settings, where years of investment have gone into building complex, mission-critical pipelines around its proven architecture.

Jenkins’ true strength lies in its ability to adapt. While newer tools might offer slicker interfaces, Jenkins gives you unparalleled control and a plugin for nearly every conceivable scenario. It’s a reliable workhorse for complex, real-world software delivery.

What This Tutorial Will Cover

I’ve designed this guide to be a hands-on journey, helping you master the practical skills you actually need for effective CI/CD. Properly integrating Jenkins is a cornerstone for following the top software development best practices and driving real growth.

Here’s a quick look at what we’ll get done:

  • Setup and Configuration: We’ll build a complete Jenkins environment from scratch, including a master server and a separate agent node for distributed builds.
  • Pipeline-as-Code: You’ll write your first Jenkinsfile, getting comfortable with both Declarative and Scripted syntax to define your automation workflow in code.
  • Testing and Artifacts: We will integrate automated tests and learn how to manage build artifacts, creating a true quality gate inside your pipeline.
  • Advanced Validation: You’ll learn a powerful technique using GoReplay to test your changes against real production traffic—a critical step most tutorials completely miss.

By the end, you won’t just know the “how,” you’ll understand the “why” behind building robust automation. For a deeper dive into the core principles, check out our guide on https://goreplay.org/blog/continuous-integration-best-practices/.

Getting Your Jenkins Environment Up and Running

Alright, let’s get our hands dirty and build the heart of our automation setup: the Jenkins server. This is the central hub, often called the master, that will orchestrate all our CI/CD pipelines. But before we get to the fun stuff, there’s one non-negotiable prerequisite: Java.

Jenkins is a Java application through and through, so you absolutely need a compatible Java Development Kit (JDK) installed. My advice? Stick with a Long-Term Support (LTS) version like JDK 17 or JDK 21 for the best stability. You can quickly check if you’re good to go by running java -version in your terminal. If you don’t see anything, get that installed first.

Installing the Jenkins Master

Getting Jenkins installed is surprisingly painless across different operating systems, since the project provides well-maintained packages. We’ll walk through the setup on a Linux server, which is where you’ll find most production Jenkins instances living.

For a Debian-based system like Ubuntu, the best way is to add the official Jenkins repository. This lets you manage Jenkins updates right alongside your other system packages.

  1. First, we need to add the repository key to the system so it trusts the source: curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee
    /usr/share/keyrings/jenkins-keyring.asc > /dev/null
  2. With the key in place, add the Jenkins repository itself to your package manager’s sources: echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]
    https://pkg.jenkins.io/debian-stable binary/ | sudo tee
    /etc/apt/sources.list.d/jenkins.list > /dev/null
  3. Finally, a quick package list update and the install command is all it takes: sudo apt-get update sudo apt-get install jenkins

Once that’s done, the Jenkins service usually starts up on its own. You can double-check with sudo systemctl status jenkins. Now, pop open a browser and head to http://your_server_ip:8080. You’ll be greeted by a setup screen asking for an initial admin password, which you can grab from the file path shown on that same page.

Configuring a Jenkins Agent for Distributed Builds

Sure, you can run builds directly on the master server, and that’s fine for tinkering. But in the real world, it’s a habit you need to break quickly. A proper, scalable setup offloads the actual work—building, testing, deploying—to separate machines called agents. This keeps your master focused on orchestration and prevents it from getting bogged down.

I’ve found the most common and reliable way to connect a Linux agent is over SSH. It’s simple and secure.

The whole point of a distributed build system is specialization. The Jenkins master is the conductor, and the agents are the orchestra. This separation is critical for security, scalability, and handling different build environments without turning your master server into a chaotic mess.

Here’s how you get a new agent hooked up:

  • Head over to Manage Jenkins > Nodes.
  • Click New Node, give it a descriptive name like linux-build-agent-01, and choose Permanent Agent.
  • Now for the important part: configuration. Under Launch method, select Launch agents via SSH.
  • You’ll need to plug in the agent machine’s IP address or hostname. Critically, you also need to add credentials for the SSH connection. Do yourself a favor and use an SSH key pair for this—it’s far more secure than passwords.

Once you save the configuration, Jenkins will reach out to the agent, copy over its required software (agent.jar), and bring it online. You’ll see its status light up in the Nodes dashboard. With your first agent ready for action, you can start telling your pipeline jobs to run on it, freeing up the master to do what it does best. This distributed architecture is the bedrock of any serious Jenkins setup.

Crafting Your First Pipeline with Jenkinsfile

Alright, with a functioning Jenkins master and agent, it’s time to get to the real heart of modern CI/CD: the Jenkinsfile. This is where we bring the concept of Pipeline-as-Code to life. Instead of clicking around in a UI, you’ll define your entire build, test, and deploy workflow in a simple text file that lives right alongside your application’s source code.

This is a complete game-changer. When you commit your Jenkinsfile to your source control repository (like Git), your build process becomes versioned, auditable, and transparent. Anyone on the team can see exactly how the application is built just by opening a file.

This approach pulls all that critical configuration out of the Jenkins UI and puts it where it belongs—with your code. It’s the standard for building repeatable and reliable automation pipelines.

Just to recap, we’ve set up a basic, distributed Jenkins environment. The process starts with a Java installation, followed by setting up the master server, and finally, connecting agents to do the heavy lifting.

A three-step flowchart showing the Jenkins setup process: Install Java, Setup Jenkins Master, and Configure Jenkins Agents.

This architecture is the foundation for the scalable pipelines we’re about to build.

Declarative vs. Scripted: The Two Flavors of Jenkinsfile

When you create a Jenkinsfile, you have two syntax options to choose from: Declarative and Scripted. They both get the job done, but they take different paths to get there. Picking the right one often comes down to the complexity of your project.

  • Declarative Pipeline: This is the modern, more structured approach, and honestly, it’s what I recommend for most teams. It gives you a clean, easy-to-read syntax with a pre-defined structure that helps you stick to best practices from the get-go.

  • Scripted Pipeline: This is the original syntax, built on a Groovy-based Domain-Specific Language (DSL). It offers immense power and flexibility. If you need to write complex conditional logic or even generate pipeline stages dynamically, Scripted is the way to go.

My advice is simple: start with Declarative Pipeline. It’s far easier to learn, and it handles the vast majority of CI/CD scenarios you’ll run into. If you hit a wall and need more power, you can always embed a script block for custom Groovy code.

Let’s look at a quick comparison to make the choice clearer.

Declarative vs Scripted Pipeline Syntax

This table breaks down the core differences to help you decide which syntax fits your project best.

FeatureDeclarative PipelineScripted Pipeline
StructureHighly structured with predefined sections (pipeline, agent, stages, steps).Free-form and flexible, resembling a standard Groovy script.
Ease of UseEasier to learn and read, making it ideal for beginners and most standard use cases.Steeper learning curve, requiring some Groovy knowledge for advanced logic.
FlexibilityMore rigid structure, though a script block allows for custom Groovy code when needed.Extremely flexible, allowing for complex programmatic logic like loops, conditionals, and dynamic stage creation.
Error CheckingSyntax is validated before the pipeline runs, catching structural errors early.Errors are often caught at runtime, which can make debugging more challenging.
Best ForStraightforward CI/CD workflows, teams new to Jenkins, and enforcing a consistent pipeline structure.Complex, dynamic, or highly customized pipelines where deep programmatic control is essential.

Ultimately, both are powerful tools in your Jenkins toolkit. Knowing when to use each one is key.

A Declarative Pipeline Example

A Declarative Jenkinsfile is defined inside a pipeline block and is neatly organized into sections like agent, stages, and steps. Each stage represents a logical chunk of your workflow, like ‘Build’ or ‘Test’.

Here’s what a basic pipeline looks like: pipeline { agent any // Tells Jenkins this can run on any available agent.

stages {
    stage('Build') {
        steps {
            // Your build commands go here.
            echo 'Building the application...'
            sh './mvnw clean package'
        }
    }
    stage('Test') {
        steps {
            // Commands for running automated tests.
            echo 'Running unit tests...'
            sh './mvnw test'
        }
    }
}

} See how clean that is? The rigid structure is a feature, not a bug. It guides you toward creating a well-organized workflow that’s easy for anyone to understand at a glance. The sh step simply executes a shell command—in this case, our Maven commands.

A Scripted Pipeline Example

Now, let’s build the same pipeline using Scripted syntax. You’ll immediately notice it feels more like a traditional script because, well, it is one. It uses pure Groovy to control the flow.

A Scripted Jenkinsfile is typically defined within a node block: node { stage(‘Build’) { // We can mix Groovy and shell commands freely here. echo ‘Building the application…’ sh ’./mvnw clean package’ } stage(‘Test’) { echo ‘Running unit tests…’ sh ’./mvnw test’ } } While this example is simple, the real power of Scripted Pipeline shines when you start adding if/else conditions, loops, and other programmatic logic directly into your pipeline. For example, you could dynamically create stages based on which files were changed in a commit.

Declarative offers simplicity and structure, while Scripted provides unmatched flexibility for those really complex automation challenges. Getting comfortable with both will make you a Jenkins pro.

Integrating Automated Testing and Artifacts

A computer monitor displays 'TEST & ARCHIVE' on a wooden desk with packaging boxes and office items.

A pipeline that only builds code is little more than a glorified script. The real power comes when you embed automated quality checks and manage the outputs—the artifacts—in a structured way.

This is what turns simple automation into a reliable, self-enforcing quality gate for your entire development process.

Let’s take the Jenkinsfile we built earlier and give it a serious upgrade. We’ll add a crucial Test stage to automatically run our unit tests, ensuring new code doesn’t break what already works. Once the tests pass, we’ll archive the application, getting it ready for deployment.

Implementing an Automated Test Stage

Adding a testing stage to your Declarative Pipeline is the natural next step after a build. It’s your first line of defense against regressions.

If the tests fail, the pipeline stops dead in its tracks. Faulty code never even gets a chance to move downstream toward production. That immediate feedback loop is priceless.

For a Java project using Maven, this is as simple as adding another stage block to our Jenkinsfile.

pipeline { agent any

stages {
    stage('Build') {
        steps {
            echo 'Building the application...'
            sh './mvnw clean package'
        }
    }
    stage('Test') {
        steps {
            echo 'Running unit tests...'
            sh './mvnw test'
        }
    }
}

}

The sh './mvnw test' command is straightforward—it just runs the default test phase in Maven. But just running tests isn’t enough. We need to see the results.

This is where Jenkins plugins really shine. A pipeline’s job isn’t just to execute commands; it’s to provide clear, actionable feedback.

Publishing test results creates a transparent record of code quality over time. It makes it easier for the whole team to spot trends, identify flaky tests, and fix issues before they become major problems.

The JUnit plugin, for instance, can parse the XML test reports that most frameworks generate. By adding a post section to our Test stage, we can tell Jenkins to grab those reports and display them right on the build dashboard.

// … inside the ‘Test’ stage … post { always { junit ‘target/surefire-reports/**/*.xml’ } } With that small addition, you get a detailed breakdown of test failures, history, and trends, all within the Jenkins UI.

Managing and Archiving Build Artifacts

Okay, our code is built and tested. Now what? The pipeline has produced a valuable output: a deployable artifact. This could be a JAR file, a Docker image, or anything in between.

Leaving this artifact sitting on the agent’s workspace is a huge missed opportunity. Proper artifact management means storing it somewhere safe, versioned, and accessible.

This is where dedicated tools like JFrog Artifactory or Sonatype Nexus typically come in, acting as the single source of truth for all your builds. For now, we’ll start with Jenkins’ own built-in archiveArtifacts step. It’s a great first step.

This step scoops up files from the agent’s workspace and saves them directly with the build record in Jenkins. Let’s add an Archive stage to our pipeline to handle this.

// … inside the stages block, after ‘Test’ … stage(‘Archive’) { steps { echo “Archiving the application…” archiveArtifacts artifacts: ‘target/*.jar’, fingerprint: true } } Let’s break down what’s happening here:

  • artifacts: 'target/*.jar': This tells Jenkins to look in the target directory and grab any file ending with .jar.
  • fingerprint: true: This is a seriously powerful feature. Jenkins generates an MD5 checksum of the file. This lets you track exactly which build produced a specific JAR, even if it gets moved or renamed across other jobs.

This entire workflow is possible because of Jenkins’ incredible extensibility. With a massive ecosystem of over 1,800 plugins, it can connect with pretty much any tool in your stack, from test frameworks to artifact repositories and security scanners.

Speaking of which, integrating security checks is just as critical. Understanding security in the software development life cycle and building those practices directly into your pipeline is essential for delivering robust, secure software.

By adding these Test and Archive stages, our pipeline now provides a complete, automated feedback loop. It validates code quality, then securely stores the result, ready for whatever comes next.

Validating Changes with Traffic-Replay Testing

Alright, your pipeline is humming along. It builds your code, nails the automated unit tests, and archives artifacts like a champ. This is a massive win and the bedrock of a solid CI process.

But let’s be honest. What about the weird edge cases? The ones that only pop up under the chaotic, unpredictable storm of real user traffic? This is where a lot of teams get a false sense of security.

Standard tests are crucial, but they run in a sterile, predictable bubble. They just can’t replicate the complex interactions, bizarre inputs, and concurrent request patterns your app will face in the wild. This gap is exactly where performance regressions and sneaky bugs love to hide, often staying invisible until a deployment goes live and real users start feeling the pain.

To get real confidence in your releases, you have to test your application against the real world. That’s where traffic-replay testing comes in.

Bridging the Gap Between Staging and Production

Traffic-replay testing, sometimes called traffic shadowing, is a powerful technique. You capture live production traffic and replay it against your staging or test environment. Instead of relying on fake, synthetic test data, you validate your new code against the exact behavior of your actual users.

This approach gives you a level of validation that traditional testing just can’t touch. It helps you answer the really tough questions before you ship:

  • Does this change introduce a memory leak when it’s under sustained load?
  • How does that new feature handle real, messy user data?
  • Will this refactor cause an unexpected storm of database queries?

By hitting your staging environment with a realistic load, you can proactively find and squash issues that would otherwise turn into production fires.

Implementing Traffic Replay with GoReplay

An excellent open-source tool for this job is GoReplay. It’s built to capture live HTTP traffic and replay it somewhere else, all without you having to change a single line of your application code. We can easily bake this right into our Jenkins pipeline by adding a new Validate with Replay stage.

This stage would usually run right after your application gets deployed to a staging environment. The Jenkins agent machine will fire off some GoReplay commands to start sniffing traffic from production and sending it over to your staging endpoint.

Here’s a rough idea of how you could add this stage to your Jenkinsfile:

stage(‘Validate with Replay’) { steps { script { // Deploy the new version to a staging environment first echo “Deploying to Staging for traffic validation…” sh ’./deploy-to-staging.sh’

        // Start replaying production traffic against the staging environment
        echo "Starting traffic replay with GoReplay..."
        sh 'gor --input-raw :8080 --output-http "http://staging-app-endpoint"'
    }
}

}

In this snippet, the gor command listens for traffic on port 8080 (pretending to be the production listener) and forwards it all to our staging app. You’d probably let this run for a set amount of time—maybe 15-30 minutes—to gather performance metrics and watch your application logs for any new errors.

By replaying real user interactions, you move from hoping a deployment will work to knowing it will withstand the pressures of production. It’s the ultimate confidence boost before hitting that final deploy button.

This method transforms your testing from a theoretical exercise into a practical dress rehearsal for production. By understanding how traffic replay improves load testing accuracy, you can build far more resilient systems. Adding this to your jenkins continuous integration tutorial workflow ensures your application isn’t just functionally correct, but truly production-ready.

Handling Credentials and Common Pipeline Issues

A production-ready pipeline has to be more than just functional; it needs to be secure and resilient. This really boils down to two things: managing your secrets like a pro and knowing how to fix things when they inevitably break. Let’s dig into both, because you’ll face them in any real-world Jenkins setup.

First things first: never, ever hardcode secrets like API keys, database passwords, or tokens directly in your Jenkinsfile. I can’t stress this enough. It’s a massive security hole that exposes sensitive information in your Git repository and, worse, potentially in your build logs for anyone to see.

The right way to handle this is with the built-in Jenkins Credentials Manager. Think of it as a secure vault inside Jenkins, designed specifically for storing and managing all your secrets centrally.

Securely Injecting Credentials

The Credentials Manager is flexible, supporting everything from simple username/password pairs to secret text files and SSH keys. Once you’ve added a secret, you can inject it safely into your pipeline as an environment variable.

In a Declarative Pipeline, you just wrap the steps that need the secret inside an environment block and use the credentials() helper. It’s pretty straightforward.

environment { // The variable ‘MY_SECRET_API_KEY’ will be available in this stage MY_SECRET_API_KEY = credentials(‘your-secret-id-from-jenkins’) } steps { sh ‘curl -H “Authorization: Bearer $MY_SECRET_API_KEY” https://api.example.com/data’ } This approach keeps the actual secret value masked in the console output, so it’s never exposed. The 'your-secret-id-from-jenkins' bit is just the unique ID you give the credential when you save it in Jenkins.

A quick pro-tip: Never print a secret variable to the console, even if you’re just debugging. Jenkins does its best to mask the value, but it’s a risky habit. The goal is to keep credentials out of logs entirely.

Decoding Common Pipeline Failures

Even with perfect credential handling, pipelines fail. It happens. Learning to troubleshoot effectively is a non-negotiable skill for anyone working with CI/CD.

Your best friend here is always the Console Output for a given build. This log gives you the step-by-step play-by-play of the execution and, most importantly, the error messages that tell you what went wrong.

Here are a few classic problems you’ll run into and what to look for:

  • Agent Connection Problems: If a job just hangs at the very beginning, check the logs for messages like “Waiting for executor” or SSH connection timeouts. This usually points to an agent that’s offline, a network hiccup, or a simple misconfiguration on the agent node.
  • Script Errors: This is a big one. Look for Java stack traces or specific command failures in the log. Any exit code other than 0 is a red flag. For instance, if a sh './mvnw test' step fails, it’s not a Jenkins problem—it means your tests are failing.
  • Permission Denied: Ah, the old classic. If your script tries to write a file or access a directory it shouldn’t, the build will crash. You need to make sure the user running the Jenkins agent process has the right permissions on the agent machine itself.

Getting a handle on these two areas—secure credential management and efficient troubleshooting—will make your pipelines more professional, secure, and a whole lot easier to maintain in the long run.

Digging Deeper: Your Top Jenkins Questions Answered

Once you get past the “hello world” stage of a Jenkins continuous integration tutorial, you’ll inevitably run into the trickier, real-world problems. Let’s tackle a few of the questions that always seem to pop up as you start to scale your pipelines.

Sooner or later, someone will complain about slow builds. When that happens, your first suspect should always be the agent configuration. Are your agents starved for CPU and memory? Even better, consider switching to ephemeral, container-based agents. They give you a pristine environment for every single run, which stops workspace clutter and leftover artifacts from grinding your builds to a halt.

How Do I Choose the Right Plugins?

With thousands of plugins available, it’s easy to go overboard. My advice? Start lean. All you really need is the Pipeline plugin, maybe Blue Ocean if you like a slicker UI, and whatever SCM plugin your team uses (like Git).

Before you click “install” on a new plugin, always ask yourself: “Can a native pipeline step do this already?” Piling on plugins you don’t truly need just creates more maintenance overhead and opens the door to potential security holes.

Stick to the popular, well-maintained plugins that get frequent updates. A quick look at a plugin’s download stats and release history tells you a lot about its reliability and community backing. Never, ever use an abandoned plugin.

It’s also worth looking at the bigger picture. The continuous integration tools market is projected to hit $2.09 billion by 2026, and something like 85% of top tech companies are already deep into CI/CD. Mastering a tool like Jenkins isn’t just a good idea—it’s essential for staying competitive. You can discover more insights on CI market growth here.

When your team starts to grow, the next logical step is moving to a “Jenkins-as-Code” model. The Jenkins Configuration as Code (JCasC) plugin is your best friend here. It lets you manage your entire Jenkins setup—from global settings to job configurations—in simple, version-controlled text files. This makes your automation infrastructure just as solid and reliable as your application code.


Gain ultimate confidence in your releases with GoReplay. Move beyond hoping a deployment works to knowing it will by validating changes against real production traffic. Start testing with real user behavior today.

Ready to Get Started?

Join these successful companies in using GoReplay to improve your testing and deployment processes.