Jenkins Course
Environment Variables
A pipeline that hardcodes server names, image tags, and deploy targets into the script is fragile and non-reusable. Environment variables are how Jenkins pipelines stay flexible — the values live in one place, the logic stays the same, and changing an environment means changing one line.
This lesson covers
Built-in Jenkins environment variables → The environment { } block → Stage-scoped variables → Accessing variables in sh steps → Setting variables from shell output → Credentials as environment variables → Variable precedence
Environment variables are key-value pairs available to every step in a pipeline. Some are provided automatically by Jenkins — the build number, the branch name, the workspace path. Others you define yourself — the app name, the target environment, the Docker registry URL. Together they make pipelines configurable without touching the logic.
The Analogy
Environment variables in a pipeline are like settings in a recipe. The recipe — the steps — stays the same: mix, bake, cool. But the settings change per batch: temperature, time, tin size. A baker who writes "bake at 180°C" into the recipe body can't easily change it for a different oven. A baker who writes "bake at ${OVEN_TEMP}" and sets the variable at the top of the recipe card can swap ovens with one change. Jenkins pipelines work exactly the same way.
Built-in Jenkins Environment Variables
Jenkins injects these into every build automatically. You can use them in any pipeline step without declaring them:
| Variable | What it contains | Example value |
|---|---|---|
| BUILD_NUMBER | Sequential build number for this job | 42 |
| BUILD_URL | Full URL to this build in the Jenkins UI | http://jenkins/job/api/42/ |
| JOB_NAME | Full name of the job including folder path | payments/checkout-service |
| BRANCH_NAME | Git branch that triggered the build | main, feature/login |
| GIT_COMMIT | Full SHA of the commit being built | a1b2c3d4e5f6... |
| WORKSPACE | Absolute path to the build workspace on the agent | /var/jenkins-agent/workspace/checkout |
| NODE_NAME | Name of the agent running the current step | agent-linux-01 |
Full list at your-jenkins/env-vars.html — every Jenkins instance has this page listing all available variables.
Declaring Custom Variables — The environment { } Block
The scenario:
You're writing a pipeline for the checkout service. It needs to build a Docker image, deploy to staging, and run a smoke test against the deployed URL. Rather than repeating the app name, registry URL, and deploy target three times across four stages, you declare them once in the environment block and reference them everywhere.
New terms in this code:
- environment { } block — a Declarative pipeline directive that defines environment variables available to all stages. Can be placed at the pipeline level (available everywhere) or inside a stage (available only in that stage).
- ${env.VARIABLE} — the Groovy syntax to reference an environment variable inside a
script { }block or in string interpolation. In ashstep,${VARIABLE}(withoutenv.) is expanded by the shell. - returnStdout: true — an option for the
shstep that captures the command's standard output as a string and returns it, rather than printing it to the console. Used to capture dynamic values like a git commit hash. - trim() — a Groovy string method that removes leading and trailing whitespace. Essential when capturing shell output — commands often add a trailing newline that breaks comparisons if not trimmed.
- credentials() — a helper in the
environment { }block that injects a stored Jenkins credential as an environment variable. The value is masked in console output — never printed in plain text.
pipeline {
agent { label 'linux && docker' }
// Pipeline-level environment variables — available in every stage
environment {
APP_NAME = 'checkout-service'
DOCKER_REGISTRY = 'registry.acmecorp.com'
DEPLOY_ENV = 'staging'
STAGING_URL = 'https://checkout.staging.acmecorp.com'
// Combine built-in Jenkins variables with your own
// BUILD_NUMBER is injected by Jenkins automatically
IMAGE_TAG = "${APP_NAME}:${BUILD_NUMBER}"
FULL_IMAGE = "${DOCKER_REGISTRY}/${IMAGE_TAG}"
// Inject a credential from the Jenkins credential store as an env var
// The value is masked in all console output — shown as ****
DOCKER_CREDS = credentials('docker-registry-credentials')
// credentials() for usernamePassword creates two variables:
// DOCKER_CREDS_USR (username) and DOCKER_CREDS_PSW (password)
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
// Capture the short git commit hash dynamically
// returnStdout: true — captures output as a string
// trim() — removes the trailing newline the shell adds
env.GIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
// Now override IMAGE_TAG to include the commit hash
env.IMAGE_TAG = "${APP_NAME}:${BUILD_NUMBER}-${env.GIT_SHORT}"
env.FULL_IMAGE = "${DOCKER_REGISTRY}/${env.IMAGE_TAG}"
echo "Building image: ${env.FULL_IMAGE}"
}
}
}
stage('Test') {
// Stage-level environment variable — only available in this stage
environment {
TEST_REPORT_DIR = "${WORKSPACE}/build/test-results"
}
steps {
sh './gradlew test'
sh "echo Test reports in: ${TEST_REPORT_DIR}"
}
}
stage('Docker Build and Push') {
steps {
sh """
# Shell variables — ${FULL_IMAGE} is expanded by the shell
docker login -u ${DOCKER_CREDS_USR} -p ${DOCKER_CREDS_PSW} ${DOCKER_REGISTRY}
docker build -t ${FULL_IMAGE} .
docker push ${FULL_IMAGE}
"""
}
}
stage('Deploy to Staging') {
steps {
sh """
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${FULL_IMAGE} \
--namespace=${DEPLOY_ENV}
"""
echo "Deployed ${FULL_IMAGE} to ${DEPLOY_ENV}"
}
}
stage('Smoke Test') {
steps {
// STAGING_URL defined at pipeline level — available here
sh "curl --fail --retry 3 ${STAGING_URL}/health"
echo "Smoke test passed for ${APP_NAME} on ${DEPLOY_ENV}"
}
}
}
post {
success {
echo "✅ ${APP_NAME} build ${BUILD_NUMBER} deployed successfully to ${DEPLOY_ENV}"
}
failure {
echo "❌ ${APP_NAME} build ${BUILD_NUMBER} failed — check ${BUILD_URL}"
}
always { cleanWs() }
}
}
Where to practice: Add an environment { } block to any existing Jenkinsfile you have. Start with one variable — APP_NAME = 'my-service' — then use ${APP_NAME} in a few echo steps and run the build to confirm they resolve. The full list of available built-in variables is at http://YOUR-JENKINS/env-vars.html on your Jenkins instance.
[Pipeline] { (Checkout) }
[Pipeline] sh
+ git rev-parse --short HEAD
a1b2c3d
[Pipeline] echo
Building image: registry.acmecorp.com/checkout-service:42-a1b2c3d
[Pipeline] { (Test) }
[Pipeline] sh
+ ./gradlew test
BUILD SUCCESSFUL — 47 tests, 0 failed
[Pipeline] sh
+ echo Test reports in: /var/jenkins-agent/workspace/checkout-service/build/test-results
Test reports in: /var/jenkins-agent/workspace/checkout-service/build/test-results
[Pipeline] { (Docker Build and Push) }
[Pipeline] sh
+ docker login -u jenkins-bot -p **** registry.acmecorp.com
Login Succeeded
+ docker build -t registry.acmecorp.com/checkout-service:42-a1b2c3d .
Successfully built 3e4f5a6b
+ docker push registry.acmecorp.com/checkout-service:42-a1b2c3d
[Pipeline] { (Deploy to Staging) }
[Pipeline] sh
+ kubectl set image deployment/checkout-service ...42-a1b2c3d --namespace=staging
deployment "checkout-service" image updated
[Pipeline] echo
Deployed registry.acmecorp.com/checkout-service:42-a1b2c3d to staging
[Pipeline] { (Smoke Test) }
[Pipeline] sh
+ curl --fail --retry 3 https://checkout.staging.acmecorp.com/health
{"status":"UP"}
[Pipeline] echo
Smoke test passed for checkout-service on staging
✅ checkout-service build 42 deployed successfully to staging
Finished: SUCCESS
What just happened?
- IMAGE_TAG was built from other variables —
IMAGE_TAG = "${APP_NAME}:${BUILD_NUMBER}"combines a custom variable with a Jenkins built-in. Then in the Checkout stage, it was overridden with the short git hash:42-a1b2c3d. Every downstream stage — Build, Deploy, Smoke Test — used the same final value without any duplication. - Credentials were masked automatically — the Docker password appeared as
****in the console output. Thecredentials()helper in theenvironment { }block registers the value with Jenkins' secret masking system. Even if the password were accidentally echoed in a script, it would still appear as****. - Stage-level variable was scoped correctly —
TEST_REPORT_DIRwas defined inside the Test stage'senvironment { }block. It resolved to the full workspace path in the Test stage, but would not be accessible in the Deploy or Smoke Test stages. Stage scoping prevents variable name collisions across a large Jenkinsfile. - Shell expansion vs Groovy expansion — inside a
shstep,${FULL_IMAGE}is expanded by the shell (Bash). Inside ascript { }block or in anechostep,${env.FULL_IMAGE}is expanded by Groovy. Both work — but knowing the difference matters when debugging a variable that resolves to empty.
Variable Precedence — Which Value Wins
When the same variable name is set in multiple places, Jenkins follows a precedence order. Highest wins:
Variables set inside a script { } block with env.VAR = value — highest precedence, set at runtime
Stage-level environment { } block — overrides pipeline-level for that stage only
Pipeline-level environment { } block — applies to all stages unless overridden
Pipeline parameters (Lesson 17) — available as ${params.PARAM_NAME}
Jenkins built-in variables (BUILD_NUMBER, BRANCH_NAME, etc.) — lowest, always available as a fallback
Watch Out — Shell vs Groovy expansion
Inside a sh step with double quotes: sh "echo ${env.APP_NAME}" — Groovy expands the variable before the shell sees it. Inside a triple-quoted string or single quotes: sh 'echo ${APP_NAME}' — the shell expands it. If a variable resolves to empty, check which expansion mechanism you're using and whether the variable was set before that point in the pipeline.
Teacher's Note
Every pipeline should have an environment { } block — even a short one. It makes the pipeline self-documenting: anyone reading the Jenkinsfile immediately sees what's configurable and what the defaults are, before reading a single stage.
Practice Questions
1. Which option must be added to a sh step to capture the command's output as a string and assign it to a variable?
2. Which helper function in the environment { } block injects a Jenkins stored credential as an environment variable and automatically masks its value in console output?
3. Which Jenkins built-in environment variable contains the sequential number of the current build for a given job?
Quiz
1. A pipeline sets DEPLOY_ENV = 'staging' at the pipeline level, but one stage needs to deploy to production. What is the correct way to override the value for that stage only?
2. What is the safest way to make a database password available to shell steps in a pipeline without it appearing in the console output?
3. When capturing shell output with returnStdout: true, why should you almost always call .trim() on the result?
Up Next · Lesson 17
Job Parameters
Environment variables set defaults — parameters let the person triggering the build change them. Strings, choices, booleans, and the patterns that keep parameterised pipelines from becoming unmaintainable.