Jenkinsfile: Defining Jenkins Pipelines as Code
Learn how to create Jenkinsfiles, use Declarative and Scripted Pipelines, manage agents and credentials, test builds, deploy safely, and maintain pipeline code.
A Jenkinsfile is a version-controlled text file that describes how Jenkins should run a Pipeline. It normally lives beside the application source code and defines activities such as checkout, build, test, packaging, artifact publication, deployment, and reporting.
This lesson assumes basic Jenkins navigation, Git branches and pull requests, command-line build commands, shell scripting, CI/CD concepts, environment variables, and secrets.
Why use a Jenkinsfile?
Without a Jenkinsfile, much of a job's behavior may be entered in Jenkins's web interface. That configuration can work, but it is harder to review, reproduce, and move between Jenkins environments. A Jenkinsfile keeps the workflow in source control as Pipeline as Code.
- Version control: pipeline changes have commits, branches, tags, and history.
- Peer review: the same review process used for application code can examine build and deployment changes.
- Repeatability: every branch can describe its own required workflow.
- Traceability: a build can be associated with the source revision and Jenkinsfile that produced it.
- Shared ownership: developers, operations teams, and release engineers can maintain the delivery process together.
A Jenkinsfile is not the Jenkins server configuration, a credential store, or a job itself. A Jenkins Pipeline job loads and executes it. Job settings still connect Jenkins to source control, select credentials, define discovery behavior, and supply server-level policies.
Where Jenkins finds the Jenkinsfile
The conventional location is the repository root, with the exact filename Jenkinsfile. Filename capitalization matters. The file is committed to the branch or revision that Jenkins builds.
- A Pipeline job can load a Jenkinsfile from configured source control or from a manually selected script source.
- A Multibranch Pipeline scans branches and pull requests, then runs the Jenkinsfile found in each discovered revision.
- An organization-level folder or organization job applies repository discovery rules and looks for the configured script path in each repository.
If the file is stored elsewhere, configure the job's Script Path, such as ci/Jenkinsfile. The path must match the repository path and branch revision exactly.
Create a Pipeline or Multibranch Pipeline job, connect it to the source repository, select the appropriate SCM credentials, and use Jenkinsfile as the script path unless the repository uses a custom location.
Pipeline fundamentals
A Pipeline is a Jenkins automation workflow made of ordered or parallel stages and steps. A stage is a visible logical phase, such as Build or Test. A step is one operation, such as running a shell command or publishing a test report.
Jenkinsfiles use Groovy-based syntax interpreted by Jenkins Pipeline plugins. Declarative syntax adds a structured model and validation rules; Scripted syntax uses more direct Groovy control flow.
A common lifecycle is:
- Checkout source code.
- Build or compile it.
- Run unit, integration, or compatibility tests.
- Package outputs.
- Publish or retain artifacts and quality results.
- Deploy to development, staging, or production.
- Verify the deployment and report the outcome.
Declarative Pipeline syntax
A Declarative Pipeline requires a top-level pipeline block and normally specifies an agent. Work is organized into stages, each containing one or more stage blocks and their steps.
pipeline {
agent any
stages {
stage('Build') {
steps {
sh './build.sh'
}
}
stage('Test') {
steps {
sh './test.sh'
}
}
}
post {
always {
junit 'reports/**/*.xml'
}
}
}
Declarative syntax is appropriate for most standard pipelines because its required structure makes intent clear and allows Jenkins to validate block placement before execution. Use the Jenkins Pipeline syntax validator when iterating on a Jenkinsfile.
| Directive or block | Purpose | Typical scope | Example use |
|---|---|---|---|
pipeline | Root Declarative container | Whole Jenkinsfile | Defines the pipeline |
agent | Selects an execution environment | Global or stage | agent any |
stages | Contains pipeline phases | Whole pipeline | Build, Test, Deploy |
stage | Names a visible phase | Inside stages | stage('Test') |
steps | Contains operations | Inside a stage | sh './test.sh' |
environment | Defines environment variables | Pipeline or stage | APP_ENV = 'test' |
parameters | Declares build inputs | Pipeline | Environment choice |
options | Controls execution behavior | Pipeline | Timeout and retention |
triggers | Requests automatic starts | Pipeline | SCM polling or schedules |
when | Conditionally runs a stage | Stage | Only on main |
post | Runs result-dependent actions | Pipeline or stage | Publish reports always |
parallel | Runs independent branches concurrently | Stage steps | Unit and integration tests |
matrix | Runs combinations of axes | Stage | Operating systems and runtimes |
Declarative directives in practice
environment can apply globally or to one stage. parameters defines values supplied by a user or trigger. triggers can schedule builds or react to source-control events, subject to the installed integrations. tools requests configured tool installations.
when supports branch, parameter, expression, change, and other conditions. input pauses for approval or information. options can set timeouts, timestamps, build retention, retry behavior, concurrency rules, and durability-related settings.
stage('Deploy production') {
when {
branch 'main'
}
input {
message 'Approve production deployment?'
}
steps {
sh './deploy.sh production'
}
}
Scripted Pipeline syntax
A Scripted Pipeline commonly starts with node, which allocates an agent and workspace, and uses stage blocks to label work. It can use Groovy variables, loops, functions, conditions, exception handling, and dynamically generated stages.
node('linux') {
try {
stage('Build') {
sh './build.sh'
}
stage('Test') {
sh './test.sh'
}
} finally {
junit allowEmptyResults: true, testResults: 'reports/**/*.xml'
cleanWs()
}
}
Declarative pipelines favor predictable structure and validation. Scripted pipelines favor flexibility and dynamic logic but require more Groovy knowledge and can become difficult to review. Choose Declarative for most conventional CI/CD workflows and Scripted when dynamic behavior or advanced error handling genuinely requires it. Keep a Jenkinsfile mostly in one style; a Declarative pipeline may use a limited script block for advanced logic.
| Aspect | Declarative Pipeline | Scripted Pipeline | Practical implication |
|---|---|---|---|
| Syntax structure | Fixed blocks and directives | Groovy code around Pipeline steps | Declarative is easier to scan |
| Flexibility | Structured, with bounded extension points | Highly flexible | Scripted handles dynamic workflows |
| Validation | Strong pre-execution validation | More errors appear during execution | Declarative catches structural mistakes earlier |
| Learning curve | Lower for standard jobs | Requires Groovy and Pipeline knowledge | Start with Declarative |
| Typical use cases | Build, test, package, deploy | Dynamic orchestration and custom logic | Match style to complexity |
| Use of Groovy logic | Limited or inside script | Central to the pipeline | Keep logic small and reviewable |
| Maintainability | Usually easier for teams | Can grow complex quickly | Extract repeated logic into libraries |
Agents, controllers, workspaces, and tools
The controller schedules jobs, coordinates execution, and manages Jenkins configuration. An agent is the worker environment where commands run. An executor is a capacity slot on an agent. Jenkins allocates a workspace, a filesystem area for the build, to the running task.
agent anyuses any available compatible agent.agent noneavoids a global allocation and requires stages to select their own agents.- A labeled agent, such as
agent { label 'linux' }, selects a provisioned capability. - A Docker-based agent supplies a defined container image and its tools.
- A stage-specific agent lets build, browser-test, or deployment work use different environments.
Choose agents with the required operating system, shell, SDKs, package managers, network access, credentials, and source availability. A container image must actually contain the commands used by the stage. Do not assume files or tools from a developer laptop exist in a clean Jenkins workspace.
Stages and common build steps
Use meaningful stages such as Checkout, Build, Test, Package, and Deploy. Stage boundaries appear in the Jenkins interface, making failures and duration hotspots easier to locate.
| Step | Purpose | Typical scenario | Platform or plugin considerations |
|---|---|---|---|
checkout | Retrieves source from SCM | Git checkout | Requires SCM configuration and credentials |
sh | Runs a Unix-like shell command | sh './build.sh' | Needs a compatible shell and executable file |
bat | Runs a Windows command | bat 'build.cmd' | Use on Windows agents |
junit | Publishes JUnit-format test results | junit 'reports/**/*.xml' | Requires compatible report files |
archiveArtifacts | Retains build outputs | Archive packages | Retention follows Jenkins policy |
stash | Temporarily saves files | Move files between stages | Best for one run, not long-term storage |
unstash | Restores stashed files | Use output on another agent | Must use the same build's stash |
withCredentials | Binds managed credentials | Authenticate to a registry | Requires permission and safe logging |
input | Waits for approval or input | Production gate | Combine with a timeout |
cleanWs | Removes workspace files | Cleanup after a build | Provided by the workspace cleanup integration |
build | Starts another Jenkins job | Trigger downstream deployment | Check permissions and parameter passing |
Environment variables, parameters, and credentials
An environment variable is a named value available to processes. A global or pipeline-level environment value applies broadly; a stage-level value limits scope. Build parameters let users or triggers supply controlled inputs.
pipeline {
agent any
environment {
APP_ENV = 'test'
}
stages {
stage('Publish') {
steps {
withCredentials([string(credentialsId: 'package-token', variable: 'PACKAGE_TOKEN')]) {
sh './publish.sh'
}
}
}
}
}
Jenkins credentials are stored and permission-controlled by Jenkins. Credentials binding injects a credential only around the steps that need it. Never hard-code passwords, tokens, private keys, or secret URLs in a Jenkinsfile.
- Disable shell tracing such as
set -xwhile secrets are present. - Do not echo environment variables containing secrets.
- Masking reduces accidental log exposure but is not perfect, especially after transformations or encoding.
- Use least-privilege credentials and rotate any secret that may have appeared in logs.
Source control and multibranch behavior
SCM configuration determines repository, revision, credentials, and checkout behavior. A Multibranch Pipeline discovers branches and pull requests and runs the Jenkinsfile from each revision. This means a branch can intentionally define a different workflow, but it also means a Jenkinsfile change can alter how that branch is built.
stage('Deploy') {
when {
branch 'main'
}
steps {
sh './deploy.sh development'
}
}
A common policy is to build and test every branch and validate pull requests, while allowing deployment only from a protected branch after required checks pass.
Artifacts, tests, and quality results
An artifact is a build output retained or transferred for later use, such as a package, binary, image metadata file, or report. Archive important outputs with retention policies that match release and compliance needs. Use stash and unstash for temporary transfer between stages in one run; use durable artifact storage or Jenkins artifact features for handoff between runs.
Publish unit-test results with junit. Jenkins can expose failures, trends, and test history. Static analysis, code coverage, and quality gates can be added through suitable tools and plugins. A quality gate should have an explicit failure policy so that teams know whether it blocks packaging or deployment.
Deployment workflow design
Separate development, staging, and production deployment stages. Use when conditions to select eligible revisions, parameters to select permitted targets, and input for a production approval gate.
- Keep environment-specific configuration outside the Jenkinsfile where appropriate, and bind only the credentials needed for that environment.
- Verify the deployment with health checks, smoke tests, or a service query.
- Plan rollback before deployment, including the version or artifact to restore.
- Notify responsible teams of deployment success, failure, abortion, and rollback.
- Put approval steps inside a bounded timeout so abandoned builds do not consume resources indefinitely.
Parallel, matrix, and reliable control flow
Use parallel for independent tasks such as separate test suites. Enable fail-fast when stopping sibling branches after one clearly makes the run invalid. Use matrix to test combinations such as operating systems and runtime versions, and exclude invalid combinations.
stage('Compatibility tests') {
matrix {
axes {
axis {
name 'RUNTIME'
values 'java17', 'java21'
}
axis {
name 'OS'
values 'linux', 'windows'
}
}
excludes {
exclude {
axis {
name 'OS'
values 'windows'
}
axis {
name 'RUNTIME'
values 'java17'
}
}
}
stages {
stage('Test') {
steps {
sh './test-compatibility.sh'
}
}
}
}
}
Reliability options include timeouts, retries for carefully selected transient operations, timestamps, build retention, concurrency controls, and appropriate pipeline durability settings. Do not blindly retry non-idempotent deployments. Use exception handling to distinguish expected recovery from a genuine failure.
Post-build handling
| Condition or result | Meaning | Recommended post-build action |
|---|---|---|
success | All required work completed | Notify success and publish release metadata |
failure | A required operation failed | Publish diagnostics and alert owners |
unstable | Build completed with quality issues | Expose test or quality results and review policy |
aborted | Execution was stopped | Release resources and record who or what stopped it |
changed | Result differs from the previous run | Notify on recovery or regression |
always | Runs regardless of result | Publish reports, collect logs, and clean the workspace |
Reusable pipeline code with shared libraries
A shared library is centrally managed Pipeline code imported into Jenkinsfiles. Global libraries can serve an organization; folder-scoped libraries limit use to a team or project. Conceptually, vars contains globally callable steps, src contains classes, and resources contains supporting files.
@Library('approved-ci-library@v2') _
standardBuildAndTest()
Extract repeated logic when many Jenkinsfiles implement the same policy or when one file becomes too large. Version libraries explicitly, review changes, and limit trusted code. A trusted library can perform operations that repository-controlled sandboxed code cannot, so its source and permissions require careful governance.
Validation, testing, and maintenance
- Validate Declarative syntax before using a change in a production job.
- Test changes in a feature branch, isolated job, or non-production deployment path.
- Review Jenkinsfile changes like application code, including security and rollback impact.
- Check Jenkins and plugin versions when syntax or steps behave unexpectedly.
- Document required agent labels, container images, tools, credentials, permissions, external services, and artifact locations.
- Keep stages small enough that logs and failures identify the responsible operation.
Security and governance
The Jenkins sandbox restricts operations in untrusted Pipeline Groovy. Some methods require administrator script approval. Prefer supported Pipeline steps over approving arbitrary APIs. If privileged logic is necessary, place carefully reviewed code in a controlled shared library rather than granting broad power to repository code.
Use least-privilege credentials, separate deployment permissions by environment, protect production branches, and require review for changes to deployment logic. A privileged job executing an unreviewed Jenkinsfile effectively gives repository contributors access to that job's capabilities. Source-control history, job permissions, approval records, and controlled libraries provide auditability.
Complete practical patterns
Parameterized deployment pipeline
pipeline {
agent any
parameters {
choice(name: 'TARGET', choices: ['development', 'staging', 'production'], description: 'Deployment target')
}
stages {
stage('Build') {
steps { sh './build.sh' }
}
stage('Deploy development') {
when { expression { params.TARGET == 'development' } }
steps { sh './deploy.sh development' }
}
stage('Deploy staging') {
when { expression { params.TARGET == 'staging' } }
steps { sh './deploy.sh staging' }
}
stage('Deploy production') {
when { expression { params.TARGET == 'production' } }
input { message 'Approve production deployment?' }
steps {
withCredentials([string(credentialsId: 'production-token', variable: 'DEPLOY_TOKEN')]) {
sh './deploy.sh production'
}
}
}
stage('Verify') {
steps { sh './verify-deployment.sh "$TARGET"' }
}
}
post {
success { echo 'Deployment completed' }
failure { echo 'Deployment failed; follow the rollback procedure' }
always { cleanWs() }
}
}
Parallel test execution
stage('Test') {
parallel failFast: true,
unit: {
timeout(time: 10, unit: 'MINUTES') {
sh './test-unit.sh'
}
},
integration: {
timeout(time: 20, unit: 'MINUTES') {
sh './test-integration.sh'
}
}
}
Troubleshooting Jenkinsfiles
No Jenkinsfile can be found
Verify that the file exists in the selected branch, that capitalization and Script Path match, and that the job points to the intended repository and revision. Confirm branch discovery and SCM credentials.
Declarative syntax fails before execution
Use syntax validation, inspect block nesting and directive scope, and check whether a plugin version supports the syntax. Move genuinely complex logic into a limited script block or a shared-library function.
A command works locally but fails on an agent
Compare operating system, shell, PATH, tool versions, permissions, workspace contents, and container image. Print safe diagnostics such as the working directory and tool versions. Ensure checkout and file-generation steps occur before the command.
Credentials are unavailable or appear in logs
Check the credential ID, scope, folder permissions, and job access. Bind secrets narrowly, avoid command tracing and echoing environment values, and rotate any exposed secret immediately. Masking is not a guarantee against every transformed representation.
A stage is skipped
Inspect when conditions, branch naming, parameter values, environment values, and earlier results. In Multibranch jobs, confirm the actual branch or pull-request metadata used by Jenkins.
Execution waits or agents are unavailable
Inspect the queue, executor capacity, agent status, labels, and cloud or container provisioning. Check whether an input step awaits approval, and add timeouts to bounded operations.
Script approval is required
Review whether the operation is necessary and safe. Prefer a supported step, or move vetted privileged behavior into a controlled trusted shared library instead of approving arbitrary repository code.
Exam-relevant notes
- A Jenkinsfile is source-controlled pipeline definition; it is not merely web-form job configuration.
- Declarative pipelines center on
pipeline,agent,stages,stage,steps, andpost. - Scripted pipelines commonly use
node,stage, Groovy control flow, and explicit exception handling. - Agents execute commands and own workspaces; the controller coordinates jobs.
- Use credential IDs and bindings, never hard-coded secrets.
- Multibranch jobs run the Jenkinsfile from each discovered branch or pull-request revision.
- Use durable artifact storage for long-lived handoff;
stashis primarily for transfer within one build. - Review sandbox approvals and trusted shared libraries as security decisions.