Jenkins

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:

  1. Checkout source code.
  2. Build or compile it.
  3. Run unit, integration, or compatibility tests.
  4. Package outputs.
  5. Publish or retain artifacts and quality results.
  6. Deploy to development, staging, or production.
  7. 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 blockPurposeTypical scopeExample use
pipelineRoot Declarative containerWhole JenkinsfileDefines the pipeline
agentSelects an execution environmentGlobal or stageagent any
stagesContains pipeline phasesWhole pipelineBuild, Test, Deploy
stageNames a visible phaseInside stagesstage('Test')
stepsContains operationsInside a stagesh './test.sh'
environmentDefines environment variablesPipeline or stageAPP_ENV = 'test'
parametersDeclares build inputsPipelineEnvironment choice
optionsControls execution behaviorPipelineTimeout and retention
triggersRequests automatic startsPipelineSCM polling or schedules
whenConditionally runs a stageStageOnly on main
postRuns result-dependent actionsPipeline or stagePublish reports always
parallelRuns independent branches concurrentlyStage stepsUnit and integration tests
matrixRuns combinations of axesStageOperating 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.

AspectDeclarative PipelineScripted PipelinePractical implication
Syntax structureFixed blocks and directivesGroovy code around Pipeline stepsDeclarative is easier to scan
FlexibilityStructured, with bounded extension pointsHighly flexibleScripted handles dynamic workflows
ValidationStrong pre-execution validationMore errors appear during executionDeclarative catches structural mistakes earlier
Learning curveLower for standard jobsRequires Groovy and Pipeline knowledgeStart with Declarative
Typical use casesBuild, test, package, deployDynamic orchestration and custom logicMatch style to complexity
Use of Groovy logicLimited or inside scriptCentral to the pipelineKeep logic small and reviewable
MaintainabilityUsually easier for teamsCan grow complex quicklyExtract 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 any uses any available compatible agent.
  • agent none avoids 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.

StepPurposeTypical scenarioPlatform or plugin considerations
checkoutRetrieves source from SCMGit checkoutRequires SCM configuration and credentials
shRuns a Unix-like shell commandsh './build.sh'Needs a compatible shell and executable file
batRuns a Windows commandbat 'build.cmd'Use on Windows agents
junitPublishes JUnit-format test resultsjunit 'reports/**/*.xml'Requires compatible report files
archiveArtifactsRetains build outputsArchive packagesRetention follows Jenkins policy
stashTemporarily saves filesMove files between stagesBest for one run, not long-term storage
unstashRestores stashed filesUse output on another agentMust use the same build's stash
withCredentialsBinds managed credentialsAuthenticate to a registryRequires permission and safe logging
inputWaits for approval or inputProduction gateCombine with a timeout
cleanWsRemoves workspace filesCleanup after a buildProvided by the workspace cleanup integration
buildStarts another Jenkins jobTrigger downstream deploymentCheck 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 -x while 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 resultMeaningRecommended post-build action
successAll required work completedNotify success and publish release metadata
failureA required operation failedPublish diagnostics and alert owners
unstableBuild completed with quality issuesExpose test or quality results and review policy
abortedExecution was stoppedRelease resources and record who or what stopped it
changedResult differs from the previous runNotify on recovery or regression
alwaysRuns regardless of resultPublish 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, and post.
  • 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; stash is primarily for transfer within one build.
  • Review sandbox approvals and trusted shared libraries as security decisions.