Managing Your Jenkins Environment Using withEnv: A Tutorial

8 min read

Jenkins’ Pipeline DSL is a powerful tool that allows you to integrate with a large variety of third-party tools. But even with this power, you will likely still find yourself needing to write your own functions to call shell commands from time to time. That’s when you can take advantage of Jenkins’ withEnv pipeline step.

You can use withEnv to set environment variables inside of your pipeline. The pipeline then makes the variables accessible to external processes spawned inside it.

In this post, we’ll go over how to take advantage of the withEnv step in Pipeline jobs. We’ll cover a few different examples and set up jobs on a Jenkins controller to see it in action.

Pipeline withEnv

Setting one or more environment variables in a pipeline is as simple as starting the block with a call to withEnv:

node {
  withEnv([
'MY_NAME_IS=Eric']) {
    sh
echo 'My name is $MY_NAME_IS'
  }
}

This job prints "My name is Eric" to the standard output.

Note that the environment variable definition is inside square brackets []. If you need to pass more than one environment variable to a job, pass withEnv an array of variables. We’ll see how that’s done below.

Let’s set up a few jobs.

Preparation for Tutorial

To run these examples, you’ll need a Jenkins controller. The screenshots included here are from Jenkins LTS 2.303.3. I installed it with the recommended plugins.

You’ll also need access to a Git repository (I'm using GitHub) for a public repo we’ll use for some of the jobs.

Jenkins withEnv 

A Simple Example

Let’s start with the example from above.

Log in to your Jenkins controller and create a new item.

Give it a name and make it a Pipeline job, then click OK.

Jenkins will take you to the job configuration page. Click on the Pipeline tab.

Jenkins takes you to the Pipeline section. Look for the Pipeline Script selector and enter this code into the text box.

node {
    withEnv(['MY_NAME_IS=Eric']) {
        sh 'echo My Name is $MY_NAME_IS'
    }
}

Click Save.

Now it’s time to run your new job. Click Build Now.

The job will run and pass very quickly. Click on the job number and then on Console Output in the dropdown menu.

Here’s the output.

Started by user Administrator
[Pipeline] Start of Pipeline
[Pipeline] node
Running on
Jenkins in /var/jenkins_home/workspace/Withenv test
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ echo My Name is Eric
My Name is Eric
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

The console log tells you what it is doing step-by-step, so you can see where it entered a block enclosed by withEnv. After it enters the block, denoted by the curly brace ({), Jenkins executes the shell command.

Since withEnv set MY_NAME_IS to Eric and provided it to the environment, the shell prints out your message.

Then you see the block close with } and // withEnv.

Let’s move on to a more advanced example.

Jenkins withEnv in a Jenkinsfile

Now we’re going to use a GitHub repository for the following Jenkins jobs. Instead of entering the pipeline code in Jenkins, the build code is under version control in a Jenkinsfile.

Create a new Pipeline job and select the Pipeline tab like before. But this time, set up the job for Git.

Enter the GitHub URL (https://github.com/egoebelbecker/jenkins_withenv.git) and point the Script Path to simple/Jenkinsfile.

Click Save, and run your new job.

The log output has a few steps now.

[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (WithEnv)
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ cd simple
+ ls one
one
+ ls two
two
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Here’s the Jenkinsfile you just ran. This job uses array syntax to pass in two environment variables.

pipeline {
   agent any

   stages {
       stage('WithEnv') {
           steps {
               withEnv(['TARGET1=one', 'TARGET2=two']) {
                   sh 'cd simple;ls $TARGET1; ls $TARGET2'
               }
           }
       }
   }
}

Since we’re inside a Jenkinsfile, we have to declare a pipeline block and the agent with which the job will run. 

Then we need to declare our stages. This job has a single stage named WithEnv. Inside that stage, we pass withEnv two environment variables using array syntax. Then we call a shell command with sh.

The log output shows us the beginning and ending of the stage, similar to how it does the withEnv block. This makes following build jobs in Jenkins logs very easy.

We have to switch to the simple directory inside the shell script since pointing the job at a Jenkinsfile in a subdirectory doesn’t change the job’s working directory. Finally, we use the environment variables in the shell commands.

Jenkins withEnv and Shell Scripts

Now, let’s use withEnv with a shell script. 

Create a new Pipeline job in Jenkins. Set it up for a Pipeline script like the previous one, but set the Script Path to the Jenkinsfile in the script subdirectory.

Run this job and look at the console output.

[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (WithEnv Script)
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ cd script
+ ./build.sh
total 8
-rw-r--r-- 1 root root 250 Nov 21 12:11 Jenkinsfile
-rwxr-xr-x 1 root root 168 Nov 21 12:14 build.sh
Created foofile
total 12
-rw-r--r-- 1 root root 250 Nov 21 12:11 Jenkinsfile
-rwxr-xr-x 1 root root 168 Nov 21 12:14 build.sh
-rw-r--r-- 1 root root   5 Nov 21 12:14 foofile
Contents of foofile:
foo!
Deleting foofile
total 8
-rw-r--r-- 1 root root 250 Nov 21 12:11 Jenkinsfile
-rwxr-xr-x 1 root root 168 Nov 21 12:14 build.sh
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

This script creates a new file named $FILENAME containing the contents of $VALUE. It lists the directory before it creates the file, after it creates the file, and then again after it deletes the file.

#!/bin/bash

if [ -z $VALUE ]; then
   echo "Need to set VALUE"
   exit 1
fi

if [ -z $FILENAME ]; then
   echo "Need to set FILENAME"
   exit 1
fi

ls -l
echo $VALUE > $FILENAME
echo "Created $FILENAME"

ls -l
echo "Contents of $FILENAME:"
cat $FILENAME

echo "Deleting $FILENAME"
rm $FILENAME
ls -l

The shell script inside this job expects two environment variables. If they’re not set, it prints an error message and exits with a value of 1. This illustrates an important best practice when passing variables to scripts. Without those variables, build.sh will either create an empty file, or the file creation and delete will fail—and the build will still pass, since bash won’t propagate the errors back to Jenkins.

So, one of the potential pitfalls of using withEnv to call external tools or scripts is that it can create invisible dependencies and add rigidity to your builds. Make sure your scripts are robust and use comments.

Jenkins withEnv and Build Options

Let’s wrap up by using withEnv to create an interactive build job. Create a new Pipeline job. Point this one at the same GitHub URL, but set the Script Path to options/Jenkinsfile.

Now add a parameter to the build configuration. You’ll find the This project is parameterized option in a list near the top of the configuration screen.

Check that entry, and a form will expand. Add a parameter named VALUE.

Then click Save and click Build with Parameters. You will be prompted for a setting for Value.

The build will run. View the build log.

[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (WithEnv Script)
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ cd script
+ ./build.sh
total 8
-rw-r--r-- 1 root root 250 Nov 21 12:51 Jenkinsfile
-rwxr-xr-x 1 root root 306 Nov 21 12:51 build.sh
Created foofile
total 12
-rw-r--r-- 1 root root 250 Nov 21 12:51 Jenkinsfile
-rwxr-xr-x 1 root root 306 Nov 21 12:51 build.sh
-rw-r--r-- 1 root root   6 Nov 21 12:57 foofile
Contents of foofile:
w00t!
Deleting foofile
total 8
-rw-r--r-- 1 root root 250 Nov 21 12:51 Jenkinsfile
-rwxr-xr-x 1 root root 306 Nov 21 12:51 build.sh
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

You can see that the script used the build parameter for the file contents.

This build job reused the script from the previous build. The only change to the Jenkinsfile is to set VALUE to the build parameter’s contents with ${VALUE}.

pipeline {
   agent any

   stages {
       stage('WithEnv Script') {
           steps {
               withEnv(["VALUE=${VALUE}", 'FILENAME=foofile']) {
                   sh 'cd script;./build.sh'
               }
           }
       }
   }
}

In this example, withEnv has demonstrated its value. You were able to reuse the same script in two different build jobs. This reduces errors and makes updating the build jobs easier since all of the logic is contained in a single script.

withEnv isn’t just a useful step for running shell scripts and shell commands. You can use it for overriding JAVA_HOME in Java builds, or you could use it to set your PATH variable to invoke the correct Maven or Python version.

Wrapping Up

In this tutorial, you used withEnv to pass environment variables to shells inside Jenkins jobs. You started with a job that echoed the variable to the console. From there, you passed them to shell scripts and finally used withEnv to pass a build parameter to a shell.

Jenkins Pipeline offers a set of powerful functions for creating advanced jobs. withEnv is one of those powerful steps. Use it to enhance your build jobs.

This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.