All Tools View Categories Blog About Contact Privacy

How to Inject Environment Variables in Jenkins (Complete Guide)

How to Inject Environment Variables in Jenkins (Complete Guide)

Hardcoding API_URL, DB_PASSWORD, or DEPLOY_ENV in a Jenkinsfile or in the job config means the same code can't move from staging to production without editing, and secrets end up in git history and console logs. Injecting environment variables in Jenkins — via the Jenkins UI, the Declarative environment {} block, Scripted withEnv(), the EnvInject plugin, credentials() binding for secrets, or from a properties file or shell output — lets one pipeline run anywhere with the right values per environment, while keeping secrets masked and values scoped to the narrowest stage or block that needs them. Getting the scope wrong (global when job would do, or withEnv outside its block) is the top cause of "variable not found" between stages.

This complete guide covers all seven ways to inject env vars in Jenkins — setting via Jenkins UI (Manage Jenkins → System), environment {} in a Declarative Pipeline, withEnv() in a Scripted Pipeline, EnvInject plugin for older freestyle setups, injecting secrets and credentials as env vars with credentials() binding, injecting from a properties file or shell script output, and the global vs job-level vs stage-level scope differences — plus common errors (variable not found, scope between stages, single vs double quotes) and how to debug them — with references to Jenkins Pipeline Syntax, withEnv Step, Using Environment Variables, EnvInject Plugin, and Using Credentials.

TL;DR — Quick Answer: For Declarative pipelines, use environment { VAR = "value" } at the top level (job scope) or inside a stage (stage scope). For Scripted, use withEnv(['VAR=value']) { sh 'echo $VAR' } (block scope) or env.VAR = "value" for Groovy global. For secrets, use withCredentials([string(credentialsId: 'id', variable: 'TOKEN')]) so Jenkins masks the value. For bulk vars, use readProperties from a file or env.VAR = sh(returnStdout: true, script: 'echo hi').trim() from a shell. Prefer the narrowest scope — stage over job over global — and check the environment variable tools for generating and validating .env files that feed Jenkins.
How to inject environment variables in Jenkins - complete guide

What Are Environment Variables in Jenkins?

Environment variables in Jenkins are key-value pairs exposed as env.VAR in Groovy and as $VAR or ${VAR} in shell steps (sh, bat). They include built-ins (BUILD_NUMBER, BUILD_URL, JOB_NAME, WORKSPACE, GIT_COMMIT) and custom values for API URLs, feature flags, deployment targets, and secrets. Per Jenkins: Using Environment Variables, they can be set at global (Manage Jenkins → System), job/pipeline (environment {}), or block/stage (withEnv, stage environment, or withCredentials) scope, and are interpolated in Groovy strings and shell steps — but with critical quoting and scope rules that cause the most common errors.

Jenkins environment variable scopes - global vs job vs stage

Global vs Job-Level vs Stage-Level Scope — The Mental Model

Scope determines where the variable is visible and who can override it. Precedence is stage > job > global — the narrowest wins.

ScopeWhere SetVisible ToUse For
GlobalManage Jenkins → System → Global properties → Environment variablesEvery job, every runTruly global constants (e.g., TOOLWASP_URL), rarely — avoid secrets here
Job / Pipelinepipeline { environment { VAR="value" } } at top levelAll stages of that pipelinePipeline-scoped config (e.g., API_URL) — preferred for most vars
Stage / Blockstage('Deploy') { environment { VAR="value" } } or withEnv(['VAR=value']) { ... }That stage or that withEnv block onlyTemporary or stage-specific (e.g., DEPLOY_ENV=canary for one stage)

Precedence example: Global DEPLOY_ENV=staging, job DEPLOY_ENV=production → the pipeline sees production (job wins). Inside withEnv(['DEPLOY_ENV=canary']) { sh 'echo $DEPLOY_ENV' }canary (stage/block wins). Outside the withEnv block, it reverts to production. This is why a variable set with withEnv is "not found" in the next stage — it was block-scoped and the block ended.

Rule: Define as narrowly as possible — stage if only one stage needs it, job if all stages need it, global only for truly global non-secrets. Global env vars are visible to every job and are not masked in logs, so never put secrets there; use credentials() for secrets.

7 ways to inject environment variables in Jenkins compared

7 Ways to Inject Environment Variables — When to Use Which

MethodSyntaxScopeSecrets Masked?Best For
Jenkins UI (System)Manage Jenkins → System → Environment variablesGlobalNoGlobal constants (rare)
Declarative environment {}environment { API_URL="https://..." }Job or StageVia credentials()★ Pipeline vars (most common)
Scripted withEnv()withEnv(['X=1']) { sh 'echo $X' }BlockVia withCredentialsScripted, temporary vars
EnvInject PluginProperties file / Groovy / UIJobNo (legacy)Old freestyle jobs
credentials() bindingwithCredentials([string(credentialsId: 'id', variable: 'TOKEN')])BlockYes (masked as ****)★ Secrets
Properties filereadProperties file: 'env.properties'JobNo (file content visible)Bulk vars from file
Shell outputenv.VAR = sh(returnStdout: true, script: 'echo hi').trim()Job (Groovy)NoDynamic from script

For secrets, always use credentials() or withCredentials — it masks the value as **** in console output. Plain environment {} or withEnv with a hardcoded secret is visible in the job config and, if echoed, in logs.

1. Setting Env Vars via Jenkins UI (Manage Jenkins → System)

Navigate to Manage Jenkins → System → Global properties → Environment variables → Add, enter Name: DEPLOY_ENV and Value: production, and save. The variable is then available as env.DEPLOY_ENV in Groovy and $DEPLOY_ENV in sh steps in every job. Per Jenkins System Configuration, this is global and requires Overall/Administer permission to change — a global change affects every job, so it is the broadest and riskiest scope.

When to use: Only for truly global, non-secret constants that every job needs — e.g., ARTIFACTORY_URL or TOOLWASP_URL. For job-specific values, prefer the pipeline's own environment {} so the value is versioned with the Jenkinsfile and not dependent on the controller's global state. Never put secrets here — they are not masked and are visible to anyone with job read permission via env in the build.

2. Declarative Pipeline — environment {} Block (Most Common ★)

Declarative pipeline environment block example

The Declarative environment directive, documented at Pipeline Syntax: environment, defines env vars at the top level (job scope) or inside a stage (stage scope). Values can be literal, interpolated from other env vars, or from credentials() for secrets.

pipeline {
  agent any
  environment {
    API_URL = "https://api.example.com"
    NODE_ENV = "production"
    // From credentials (secret, masked)
    AWS_SECRET = credentials('aws-secret-key')
    // Interpolation from another env var
    FULL_URL = "https://${ENV}.example.com" // ENV from global or earlier
  }
  stages {
    stage('Build') {
      steps { sh 'echo $API_URL' } // sees https://api.example.com
    }
    stage('Deploy Canary') {
      environment { DEPLOY_ENV = "canary" } // overrides job-level for this stage
      steps { sh 'echo $DEPLOY_ENV' } // canary
    }
    stage('Deploy Prod') {
      steps { sh 'echo $DEPLOY_ENV' } // production (job-level, since stage had no override)
    }
  }
}

Key details:

  • Scope: Top-level environment → all stages; stage-level environment → that stage only and overrides top-level for that stage (precedence: stage > job > global).
  • Interpolation: "https://${ENV}.example.com" uses Groovy double quotes with ${ENV} — single quotes 'https://${ENV}.example.com' do not interpolate and yield the literal string. This is the top source of "variable not found" when the value looks like ${ENV} literally.
  • Credentials in environment: AWS_SECRET = credentials('aws-secret-key') is the Declarative shorthand for withCredentials — the value is masked as **** in logs. For username/password, use credentials('docker-hub') which exposes DOCKER_HUB_USR and DOCKER_HUB_PSW.
  • When not to use: For truly dynamic values computed at runtime (e.g., from a shell), use env.VAR = sh(...).trim() in a script block, since environment {} is evaluated at pipeline start and cannot call sh.

3. Scripted Pipeline — withEnv() and Groovy env

Scripted pipeline withEnv example

In Scripted Pipeline, per withEnv Step, withEnv sets env vars for a block:

node {
  withEnv(['MY_VAR=hello', "OTHER=${env.BUILD_NUMBER}"]) {
    sh 'echo $MY_VAR' // hello
    sh 'echo $OTHER'  // 123
  }
  sh 'echo $MY_VAR' // not found — outside withEnv block
}

Block scope is the critical difference from Declarative: withEnv vars disappear after the closing } — they are not visible in the next stage or even the next statement outside the block. This is why "variable not found between stages" happens with withEnv — the fix is to use environment {} (job scope) or Groovy global env.VAR = "value" which persists for the rest of the run:

node {
  env.MY_VAR = "hello" // Groovy global, visible for the rest of the run
  stage('One') { sh 'echo $MY_VAR' } // hello
  stage('Two') { sh 'echo $MY_VAR' } // still hello
}

Groovy env is the most flexible for Scripted — it can be set from any Groovy expression, including shell output: env.GIT_HASH = sh(script: 'git rev-parse HEAD', returnStdout: true).trim(). Unlike withEnv, it is not automatically cleaned up, so it persists until the run ends or is overwritten.

4. EnvInject Plugin — Still Widely Used in Older Freestyle Setups

The EnvInject Plugin predates Pipeline and is still common in freestyle jobs and older controllers. It injects env vars at job startup from a properties file, a Groovy script, or key-value pairs in the job config. For Pipeline, it is largely superseded by environment {} and withEnv, but it remains in freestyle where Pipeline directives are not available.

How it works for freestyle: In the job config → Build Environment → Inject environment variables, set Properties File Path (env.properties) or Properties Content (MY_VAR=hello), or a Groovy script that returns a map. At build start, EnvInject loads the file or script and injects the vars for that build.

When to use vs avoid: Use for freestyle jobs that cannot be converted to Pipeline yet. For new Pipeline jobs, prefer environment {} (Declarative) or withEnv/env (Scripted) — they are versioned with the Jenkinsfile, work with credentials(), and don't require a plugin that is in maintenance mode. EnvInject does not mask secrets in logs, so don't use it for secrets — use credentials().

5. Injecting Secrets and Credentials as Env Vars — credentials() Binding (The Secure Way)

Injecting secrets as env vars with credentials binding

Never put secrets in plain environment {} or withEnv — they are visible in the job config (for Declarative, in the Jenkinsfile if hardcoded) and, if echoed, in console logs. Jenkins Credentials with binding is the secure path: the secret is stored encrypted in Jenkins (via the Credentials plugin), referenced by ID, and masked as **** in logs.

Declarative — credentials() in environment {}

pipeline {
  agent any
  environment {
    // Secret text (e.g., API token)
    AWS_SECRET = credentials('aws-secret-key')
    // Username/password — exposes two vars: DOCKER_HUB_USR and DOCKER_HUB_PSW
    DOCKER_HUB = credentials('docker-hub-creds')
  }
  stages {
    stage('Deploy') {
      steps {
        sh 'echo $AWS_SECRET' // logs as ****, not the real value
        sh 'echo $DOCKER_HUB_USR' // username, not masked separately? Actually both masked
      }
    }
  }
}

Scripted — withCredentials

withCredentials([
  string(credentialsId: 'aws-secret-key', variable: 'AWS_SECRET'),
  usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'USER', passwordVariable: 'PASS')
]) {
  sh 'echo $AWS_SECRET' // ****
  sh 'docker login -u $USER -p $PASS'
}

Supported bindings include string (secret text), usernamePassword, file (secret file), and sshUserPrivateKey. The variables are only available inside the environment or withCredentials block and are masked — even sh 'env | sort' will show **** for them. For non-secret vars, use plain environment or withEnv.

6. Injecting Vars from a Properties File or Shell Script Output

From a Properties File — readProperties

For bulk vars, a .properties or .env file is cleaner than dozens of environment {} lines. In Declarative, use a script block with readProperties:

pipeline {
  agent any
  stages {
    stage('Load Env') {
      steps {
        script {
          def props = readProperties file: 'env.properties' // key=value per line
          env.API_URL = props['API_URL']
          env.NODE_ENV = props['NODE_ENV']
        }
        sh 'echo $API_URL' // available for the rest of the run (env is global in Groovy)
      }
    }
  }
}

The file is typically API_URL=https://api.example.com\nNODE_ENV=production — the same format as .env and loadable via the environment variable tools that generate and validate .env files from CSV or JSON. Note: readProperties is a Pipeline step that reads from the workspace — the file must be checked out first (checkout scm is implicit in Declarative).

From Shell Script Output — sh(returnStdout: true)

For dynamic values computed at runtime (e.g., Git hash, build timestamp, or a value from a script):

pipeline {
  agent any
  stages {
    stage('Set Dynamic') {
      steps {
        script {
          env.GIT_HASH = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
          env.BUILD_TIME = sh(script: 'date -u +%Y-%m-%dT%H:%M:%SZ', returnStdout: true).trim()
        }
        sh 'echo $GIT_HASH' // e.g., a1b2c3d
      }
    }
  }
}

The .trim() is essential — sh with returnStdout: true includes the trailing newline, which would otherwise become part of the variable (e.g., "a1b2c3d\n") and break URLs or tags. This pattern is also how to bridge EnvInject-style file injection in Pipeline: write the shell output to a file, then readProperties it.

Common Errors — Variable Not Found and Scope Between Stages

Common Jenkins env var errors - variable not found and scope between stages

1. withEnv Variable Not Found in the Next Stage

Symptom: withEnv(['MY_VAR=hello']) { sh 'echo $MY_VAR' } works, but the next stage's sh 'echo $MY_VAR' prints empty.

Cause: withEnv is block-scoped — the variable disappears after the closing }.

Fix: For job-wide vars, use top-level environment { MY_VAR="hello" } (Declarative) or env.MY_VAR = "hello" (Scripted Groovy global) which persists for the rest of the run.

2. Single Quotes Don't Interpolate — Variable Literally ${ENV}

Symptom: environment { URL = 'https://${ENV}.example.com' } yields the literal string https://${ENV}.example.com, not https://prod.example.com.

Cause: Groovy single quotes ('...') are plain strings; double quotes ("...") are GStrings that interpolate ${VAR}. The same applies to sh 'echo $VAR' vs sh "echo $VAR" — but note: sh "echo $VAR" interpolates in Groovy before the shell sees it, so sh "echo ${env.VAR}" is often safer to let the shell expand.

Fix: Use double quotes for interpolation: environment { URL = "https://${ENV}.example.com" } and sh "echo ${env.URL}" or sh 'echo $URL' (shell expansion, not Groovy).

3. Env Var Set in One Stage Not Visible in the Next

Symptom: stage('One') { steps { script { env.FOO = "bar" } } } then stage('Two') { steps { sh 'echo $FOO' } } sometimes shows empty in Declarative.

Cause: In Declarative, each stage can run on a different agent — env is Groovy global per run, but if stages use different agent labels, the env may not propagate as expected. Also, env set inside a script block is Groovy global, but its timing relative to Declarative's environment evaluation matters.

Fix: Set job-level environment { FOO="bar" } at the top, or use withEnv around the stages that need it, or stash the value in a file and readProperties in the next stage.

4. Properties File Not Loaded — "No such file" or Empty

Symptom: readProperties file: 'env.properties' fails with "No such file" or returns empty.

Cause: The file hasn't been checked out yet, or the path is wrong, or the file uses the wrong delimiter (key: value instead of key=value).

Fix: Ensure checkout scm (implicit in Declarative) has run before readProperties, use key=value per line without spaces around :, and handle line endings (CRLF vs LF) — the plugin handles both, but hand-written files with : will be parsed as one key.

5. Credentials Not Masked — Secret Visible in Log

Symptom: sh 'echo $MY_SECRET' where MY_SECRET was set via plain environment { MY_SECRET="s3cr3t" } prints the real value.

Cause: Only credentials() / withCredentials vars are masked; plain env vars are not, even if the value is a secret.

Fix: Store the secret in Jenkins → Manage Jenkins → Credentials, then reference via credentials('id') in environment or withCredentials — the log will show ****.

6. EnvInject Works in Freestyle but Not in Pipeline

Symptom: EnvInject injects in a freestyle job but the same properties file does nothing in a Pipeline.

Cause: EnvInject is a freestyle Build Environment; Pipeline has its own environment and withEnv that do not read EnvInject's injection.

Fix: For Pipeline, migrate to environment {} or readProperties + env; keep EnvInject only for freestyle jobs that cannot be converted.

Best Practices — Where to Define What

Var TypeWhere to DefineWhy
Non-secret, pipeline-scopedenvironment {} at top levelVersioned with Jenkinsfile, visible to all stages
Non-secret, stage-scopedenvironment {} inside that stage or withEnv around the stageLeast blast radius, no leak to other stages
Secret (any scope)credentials() in environment or withCredentials blockEncrypted at rest, masked in logs, auditable
Bulk from filereadProperties + env.KEY = value in a script blockOne file for many vars, still versioned
Dynamic from shellenv.VAR = sh(returnStdout: true, script: '...').trim()Computed at runtime (e.g., Git hash)

Per Jenkins Pipeline Best Practices and CloudBees: Best Practices, keep the Jenkinsfile declarative where possible (for Blue Ocean visualization and restartability), use Scripted only when withEnv or Groovy logic is needed, and never put secrets in plain env vars or in the global System config.

FAQs About Injecting Env Vars in Jenkins

How do I set an environment variable for all Jenkins jobs?

Use Manage Jenkins → System → Global properties → Environment variables for a global var, but prefer job-level environment {} for pipeline-specific values to avoid global leakage. Global vars require admin and affect every job.

What is the difference between environment { } and withEnv()?

environment {} is Declarative, job or stage scoped, and evaluated at pipeline start (no sh inside). withEnv() is Scripted, block scoped, and can wrap any block including sh. For job-wide vars, environment {} persists; withEnv disappears after its closing brace.

How do I use secrets as env vars in Jenkins?

Store the secret in Manage Jenkins → Credentials, then reference via environment { TOKEN = credentials('token-id') } (Declarative) or withCredentials([string(credentialsId: 'token-id', variable: 'TOKEN')]) { ... } (Scripted). Jenkins masks the value as **** in logs.

How do I load env vars from a properties file?

In Pipeline, use script { def props = readProperties file: 'env.properties'; env.KEY = props['KEY'] } after checkout. The file is KEY=VALUE per line. For freestyle, EnvInject can load the file at build start.

Why is my env var not found in the next stage?

It was likely set with withEnv (block scope) or inside one stage's script without persisting. Use top-level environment {} or Groovy global env.VAR set in an early stage (or before stages) so it persists for the run.

Should I use EnvInject for Pipeline?

No — EnvInject is for freestyle; for Pipeline, use environment {}, withEnv, or readProperties + env. EnvInject is in maintenance mode for Pipeline and does not mask secrets.

Conclusion

Injecting environment variables in Jenkins is choosing the narrowest scope that fits — global via System for truly global constants, job-level environment {} for pipeline-wide config, stage-level environment or withEnv for temporary values, and credentials() / withCredentials for anything secret — with properties files and shell output bridging bulk and dynamic cases. Understanding that withEnv is block-scoped, that single quotes don't interpolate, and that global is visible everywhere prevents the "variable not found between stages" that is the most common Jenkins env var bug.

Start with environment {} for the pipeline's non-secret config, add credentials() for secrets, and reach for withEnv or readProperties only when the value is temporary or from a file — then verify the scope with an env | sort in a sh step before the next stage needs it.