Jenkins Pipeline
Jenkins pipelines turn continuous integration and delivery from a collection of manually configured jobs into a repeatable, reviewable process. Instead of defining every build and deployment action through the Jenkins UI, teams can describe the workflow in code and commit it alongside the application. This approach—commonly called Pipeline as Code—gives the delivery process a version history, supports code review, and makes changes easy to trace and roll back.
A typical Jenkins pipeline is defined in a Jenkinsfile stored at the root of the source repository. The main building blocks are:
- Jenkinsfile: The source file containing the pipeline definition.
- Agent: The machine, container, or cloud environment where pipeline tasks execute.
- Stage: A meaningful phase such as checkout, build, test, or deployment.
- Step: An individual operation performed within a stage, such as running a shell command or invoking Maven.
- Postcondition: Cleanup, notification, or reporting logic that runs after a stage or the complete pipeline.
Stages make pipeline results easier to understand. When a deployment fails, the Jenkins interface can clearly show which phase failed and how long it took. Steps provide the finer-grained actions that actually perform the work.
Declarative and Scripted Pipelines
Jenkins supports two primary pipeline syntaxes:
- Declarative pipeline: Uses a fixed, highly structured syntax. It is easier to read, validate, and maintain, making it the preferred choice for most teams.
- Scripted pipeline: Uses Groovy to control the workflow programmatically. It offers greater flexibility, but complex scripts can become difficult to test and maintain.
Declarative syntax is usually sufficient for standard CI/CD workflows:
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building the application'
}
}
}
}
Here, agent any allows the pipeline to run on any available Jenkins executor. The stages block contains one or more named stages, while each stage uses steps to define the commands or actions to execute.
A Practical Jenkinsfile
The following pipeline demonstrates a complete build, test, and deployment workflow:
pipeline {
agent any
options {
timestamps()
disableConcurrentBuilds()
timeout(time: 30, unit: 'MINUTES')
}
environment {
APP_ENV = 'staging'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh './mvnw clean package -DskipTests'
}
}
stage('Test') {
steps {
sh './mvnw test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
input message: 'Deploy the application to staging?'
sh './deploy.sh'
}
}
}
post {
success {
echo 'Pipeline completed successfully'
}
failure {
echo 'Pipeline failed'
}
always {
cleanWs()
}
}
}
Several useful controls appear in this example:
optionsconfigures timestamps, timeouts, and concurrency behavior. Disabling concurrent builds prevents two versions of the same branch from competing for the same resources.environmentdefines variables available throughout the pipeline or within an individual stage.whenapplies conditional logic. In this case, deployment runs only for themainbranch.inputpauses the pipeline until an authorized user approves the deployment.postcentralizes notifications, report publishing, and workspace cleanup.
Stage-level post blocks are especially useful for collecting test results or diagnostic files regardless of whether the stage succeeds.
Important Directives
Jenkins provides several directives for controlling pipeline behavior:
agentselects the execution environment. Common choices includeany,none, a specific label, or a Docker agent.parametersexposes user-defined values, such as a target environment, release version, or feature flag.whenevaluates branch names, tags, commit messages, parameters, or environment conditions.parallelruns independent stages concurrently to reduce overall execution time.retryrepeats a step when it fails, which can help with temporary infrastructure problems.credentialsorwithCredentialsretrieves sensitive values from Jenkins without embedding them in source code.
Sensitive information should always be handled through Jenkins credentials:
stage('Publish') {
steps {
withCredentials([
string(
credentialsId: 'publishing-token',
variable: 'PUBLISH_TOKEN'
)
]) {
sh './publish.sh'
}
}
}
The credential is injected only while the relevant command runs and is masked from normal build logs.
Multibranch Pipelines
A Multibranch Pipeline is commonly used for projects with active development branches and pull requests. Jenkins scans the configured repository and creates pipeline jobs for the branches and pull requests it discovers. Each job loads the corresponding Jenkinsfile and runs independently.
This model fits well with Git Flow, trunk-based development, and pull-request validation. Teams can automatically run tests for every proposed change and restrict deployments to protected or release branches.
Because each branch may contain a different Jenkinsfile, changes to the pipeline definition can be tested in the same way as application code. The Jenkinsfile linter also helps identify syntax errors before a commit reaches the main branch.
Shared Pipeline Libraries
When several repositories use similar build, test, notification, and deployment processes, duplicated Jenkinsfiles quickly become difficult to maintain. A Jenkins Shared Library centralizes this logic in a separate, version-controlled repository.
A shared library generally contains:
vars/: Global variables and reusable custom steps.src/: Groovy classes that implement more complex behavior.resources/: Static files required by the library.
A pipeline can load the library with an annotation:
@Library('company-pipeline-library') _
pipeline {
agent any
stages {
stage('Test') {
steps {
runUnitTests()
}
}
}
}
Shared libraries reduce duplication and help establish consistent delivery standards across teams. For maximum stability, production pipelines should reference an immutable library version rather than an uncontrolled branch.
Best Practices
Reliable Jenkins pipelines follow a few important practices:
- Store the
Jenkinsfilein version control and review it like application code. - Prefer declarative syntax unless a scripted section is genuinely necessary.
- Use least-privileged accounts for source control, testing, and deployment.
- Keep passwords, API tokens, and signing keys out of pipeline files.
- Apply timeouts, concurrency controls, and targeted retries to prevent resource exhaustion.
- Separate building, testing, and deploying into clearly named stages.
- Use containerized agents to make build environments more consistent.
- Archive important reports, logs, and artifacts for troubleshooting.
- Clean temporary workspaces without deleting reusable dependency caches.
- Require manual approval before production deployments.
- Test pipeline changes across representative branches before merging them.
With a well-structured Jenkinsfile, controlled execution environments, centralized credentials, and reusable shared libraries, Jenkins pipelines can make software delivery faster, more predictable, and fully traceable from commit to production.