When a Jenkins pipeline starts behaving unpredictably across different nodes, the culprit is often not the logic—it’s how environment variables and agents are being used. These two concepts are tightly connected, especially in distributed builds.
Let’s get into how they actually work together in real pipelines.
Why Environment Variables Behave Differently Across Agents
Jenkins pipelines often run across multiple agents (also called nodes). Each agent can have its own OS, installed tools, and system-level environment variables.
This means the same variable might resolve differently depending on where the step runs.
For example:
1pipeline {
2 agent any
3 stages {
4 stage('Check Node Info') {
5 steps {
6 sh 'echo Running on $NODE_NAME'
7 sh 'echo Home directory: $HOME'
8 }
9 }
10 }
11}If Jenkins schedules this on different agents, $HOME and even tool paths can vary significantly.
Defining Environment Variables in Pipelines
Jenkins provides multiple ways to define environment variables. The scope depends on where you declare them.
Global Pipeline Scope
1pipeline {
2 agent any
3 environment {
4 APP_ENV = 'production'
5 BUILD_VERSION = '1.0.${BUILD_NUMBER}'
6 }
7 stages {
8 stage('Print Env') {
9 steps {
10 sh 'echo $APP_ENV'
11 sh 'echo $BUILD_VERSION'
12 }
13 }
14 }
15}These variables are available across all stages and agents unless overridden.
Stage-Level Variables
Sometimes you want isolation:
1stage('Test') {
2 environment {
3 APP_ENV = 'test'
4 }
5 steps {
6 sh 'echo Running in $APP_ENV'
7 }
8}This overrides the global value only within the stage.
Agents: More Than Just "Where Code Runs"
Agents define where your pipeline executes, but also influence:
- Available environment variables
- Installed dependencies
- Filesystem structure
Here’s a simple agent declaration:
1pipeline {
2 agent { label 'linux' }
3 stages {
4 stage('Build') {
5 steps {
6 sh 'make build'
7 }
8 }
9 }
10}Now compare that with a multi-agent pipeline.
Switching Agents Mid-Pipeline
A common real-world setup: build on one agent, test on another.
1pipeline {
2 agent none
3 stages {
4 stage('Build') {
5 agent { label 'builder' }
6 steps {
7 sh 'echo Building on $NODE_NAME'
8 }
9 }
10 stage('Test') {
11 agent { label 'tester' }
12 steps {
13 sh 'echo Testing on $NODE_NAME'
14 }
15 }
16 }
17}Here’s where things get interesting: environment variables do not automatically persist across agents unless explicitly passed or stored.
Passing Data Between Agents
Since agents may not share memory or filesystem, you need a strategy.
Option 1: Use Artifacts
1stage('Build') {
2 agent { label 'builder' }
3 steps {
4 sh 'echo version=1.2.3 > build.env'
5 archiveArtifacts artifacts: 'build.env'
6 }
7}
8
9stage('Test') {
10 agent { label 'tester' }
11 steps {
12 unstash 'build.env'
13 sh 'cat build.env'
14 }
15}Option 2: Use stash/unstash
1stage('Build') {
2 steps {
3 sh 'echo 1.2.3 > version.txt'
4 stash name: 'version', includes: 'version.txt'
5 }
6}
7
8stage('Deploy') {
9 steps {
10 unstash 'version'
11 sh 'cat version.txt'
12 }
13}This is often the cleanest way to transfer data between agents.
Dynamic Environment Variables
You can compute variables at runtime using scripts.
1pipeline {
2 agent any
3 stages {
4 stage('Generate Env') {
5 steps {
6 script {
7 env.TIMESTAMP = sh(script: 'date +%s', returnStdout: true).trim()
8 }
9 sh 'echo $TIMESTAMP'
10 }
11 }
12 }
13}This is especially useful when values depend on runtime conditions.
Common Pitfall: Expecting Shell Exports to Persist
This trips up many developers.
1sh 'export MY_VAR=hello'This will not persist across steps because each sh runs in its own shell.
Instead, use:
1script {
2 env.MY_VAR = 'hello'
3}Working with Credentials as Environment Variables
Jenkins integrates credentials securely into environment variables.
1pipeline {
2 agent any
3 environment {
4 API_KEY = credentials('my-api-key-id')
5 }
6 stages {
7 stage('Use Secret') {
8 steps {
9 sh 'curl -H "Authorization: Bearer $API_KEY" https://api.example.com'
10 }
11 }
12 }
13}This avoids hardcoding sensitive values.
Combining Agents and Environment for Tooling
Different agents often have different tools installed. You can adapt environment variables accordingly.
1stage('Build') {
2 agent { label 'nodejs' }
3 environment {
4 PATH = "/usr/local/node/bin:${env.PATH}"
5 }
6 steps {
7 sh 'node -v'
8 }
9}This ensures your pipeline behaves consistently regardless of the underlying agent setup.
A Few Practical Tips
- Keep environment variables declarative when possible
- Avoid relying on agent-specific system variables unless necessary
- Use
stash/unstashfor cross-agent data transfer - Prefer
env.VARover shell exports - Test pipelines on multiple agents to catch inconsistencies early
Wrapping It Up
Environment variables and agents in Jenkins are simple individually, but their interaction introduces subtle complexity. Once you understand scope, persistence, and execution context, your pipelines become far more predictable—and easier to debug.
If a pipeline behaves differently across nodes, don’t just inspect the code. Look at the environment and the agent it runs on—that’s usually where the real story is.