120

Is it possible to conditionally set an ENV variable in a Dockerfile based on the value of a build ARG?

Ex: something like

ARG BUILDVAR=sad
ENV SOMEVAR=if $BUILDVAR -eq "SO"; then echo "hello"; else echo "world"; fi

Update: current usage based on Mario's answer:

ARG BUILD_ENV=prod
ENV NODE_ENV=production
RUN if [ "${BUILD_ENV}" = "test" ]; then export NODE_ENV=development; fi

However, running with --build-arg BUILD_ENV=test and then going onto the host, I still get

docker run -it mycontainer bin/bash
[root@brbqw1231 /]# echo $NODE_ENV
production
Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
  • 1
    Try `RUN if [ "$BUILD_ENV" = "test" ]; then export NODE_ENV=development; fi` . Mind the whitespaces. Do not remove them – Alkis Kalogeris May 05 '16 at 22:20
  • @alkis didn't seem to help though good catch on using `=` instead of `eq` – Matthew Herbst May 18 '16 at 02:19
  • 1
    Export variable during image build will not expand at run-time of container, instead use `RUN if [ "${BUILD_ENV}" = "test" ] ;then echo BUILD_ENV=development >>/etc/environment ; fi` – kyb May 17 '19 at 20:54

8 Answers8

103

Yes, it is possible, but you need to use your build argument as flag. You can use parameter expansion feature of shell to check condition. Here is a proof-of-concept Docker file:

FROM debian:stable
ARG BUILD_DEVELOPMENT
# if --build-arg BUILD_DEVELOPMENT=1, set NODE_ENV to 'development' or set to null otherwise.
ENV NODE_ENV=${BUILD_DEVELOPMENT:+development}
# if NODE_ENV is null, set it to 'production' (or leave as is otherwise).
ENV NODE_ENV=${NODE_ENV:-production}

Testing build:

docker build --rm -t env_prod ./
...
docker run -it env_prod bash
root@2a2c93f80ad3:/# echo $NODE_ENV 
production
root@2a2c93f80ad3:/# exit
docker build --rm -t env_dev --build-arg BUILD_DEVELOPMENT=1 ./
...
docker run -it env_dev bash
root@2db6d7931f34:/# echo $NODE_ENV
development
jmahlke
  • 13
  • 4
Ruslan Kabalin
  • 6,580
  • 2
  • 28
  • 20
  • 2
    This seems like the best solution, especially when building something like Nuxt.js/next.js where the NODE_ENV needs to be set at build time, not run time. – Nick Bolles Sep 11 '18 at 03:02
  • After a little more use with this, there are some caveats to ARG, it is only used for the next stage (line in the dockerfile) so you have to include it multiple times if you want to use it multiple times https://docs.docker.com/engine/reference/builder/#using-arg-variables – Nick Bolles Sep 11 '18 at 03:13
  • 2
    @Sup3rb0wlz That warning is about multi-stage builds i.e. multiple "FROM ..." lines. If you aren't using those then the ARG will continue on all future lines of the Dockerfile. – Arthur Tacca Jan 20 '19 at 11:32
  • 3
    Note that variables in Dockerfiles only allow a [limited subset of expansion types](https://docs.docker.com/engine/reference/builder/#environment-replacement), not the whole list in that link (which is for bash, not docker). Basically, the only two types allowed are the + form and – form, which are shown in this answer. – Arthur Tacca Jan 20 '19 at 12:08
  • OMG, I spent half of my day trying to figure out how I can add if/else in `Dockerfile` tried all bash code but still as unsuccessful because there is a difference in syntax of `if` condition. You lines of code just worked perfectly without needing to use `if/esle`. thanks man. – A.J. Aug 22 '22 at 04:58
27

You cannot run bash code in the Dockerfile directly, but you have to use the RUN command. So, for example, you can change ENV with RUN and export the variable in the if, like below:

ARG BUILDVAR=sad 
RUN if [ "$BUILDVAR" = "SO" ]; \
    then export SOMEVAR=hello; \
    else export SOMEVAR=world; \
    fi 

I didn't try it but should work.

OZZIE
  • 6,609
  • 7
  • 55
  • 59
Mario Cairone
  • 1,071
  • 7
  • 11
6

While you can't set conditional ENV variables but you may be able to acomplish what you are after with the RUN command and a null-coalescing environment variable:

RUN node /var/app/current/index.js --env ${BUILD_ENV:-${NODE_ENV:-"development"}}
Community
  • 1
  • 1
Steven de Salas
  • 20,944
  • 9
  • 74
  • 82
  • 1
    I like this, but it's limited to conditions that can be determined outside of the container (at run time), which is why I'm not going to accept it as "the" answer. I think a full solution to this should be able to work inside of the container (unless it can be shown that that isn't possible). Thanks for the learning regarding null-coalescing in bash though! – Matthew Herbst Feb 16 '17 at 05:29
6

Your logic is actually correct. The problem here is that RUN export ... won't work in a Dockerfile because the export command won't persist across images. Dockerfiles create a temporary container in order to generate the image for it, therefore the environment variables won't exist.

ENV on the other hand as per the documentation states:

The environment variables set using ENV will persist when a container is run from the resulting image.

The only way to do this is during your docker run command when generating the container from your image, and wrap your logic around that:

if [ "${BUILD_ENV}" = "test" ]; then
    docker run -e NODE_ENV=development myimage
else
    docker run myimage
fi
Adel Helal
  • 642
  • 1
  • 9
  • 20
3

If we are talking only about environment variable, then just set it with production

ENV NODE_ENV prod

And during container start in development, you may use -e NODE_ENV=dev.

This way image is always built-in production but the local container is launched in development.

Sergey Romanov
  • 2,949
  • 4
  • 23
  • 38
2

This answer is great if you only need to check whether a build-arg is present and you want to set a default value. To improve this solution, in case you want to use the data passed by the build-arg, you can do the following:

FROM debian:stable
ARG BUILD_DEVELOPMENT=production
ENV NODE_ENV=$BUILD_DEVELOPMENT

The magic comes from the default value for the ARG.

LBR8
  • 61
  • 4
0

Passing values to Dockerfile and then to entrypoint script

From the command line pass in your required value (TARG)

docker run --env TARG=T1_WS01 -i projects/msbob

Then in your Dockerfile put something like this

Dockerfile:
# if $TARG is not set then "entrypoint" defaults to Q0_WS01
CMD ./entrypoint.sh ${TARG} Q0_WS01

The entrypoint.sh script only reads the first argument

entrypoint.sh:
#!/bin/bash
[ $1 ] || { echo "usage: entrypoint.sh <$TARG>" ; exit ; }
target_env=$1
roblogic
  • 1,266
  • 2
  • 13
  • 23
0

I had a similar issue for setting proxy server on a container.

The solution I'm using is an entrypoint script, and another script for environment variables configuration. Using RUN, you assure the configuration script runs on build, and ENTRYPOINT when you run the container.

--build-arg is used on command line to set proxy user and password.

The entrypoint script looks like:

#!/bin/bash
# Load the script of environment variables
. /root/configproxy.sh
# Run the main container command
exec "$@"

configproxy.sh

#!/bin/bash

function start_config {
read u p < /root/proxy_credentials

export HTTP_PROXY=http://$u:$p@proxy.com:8080
export HTTPS_PROXY=https://$u:$p@proxy.com:8080

/bin/cat <<EOF > /etc/apt/apt.conf 
Acquire::http::proxy "http://$u:$p@proxy.com:8080";
Acquire::https::proxy "https://$u:$p@proxy.com:8080";
EOF
}

if [ -s "/root/proxy_credentials" ]
then
start_config
fi

And in the Dockerfile, configure:

# Base Image
FROM ubuntu:18.04

ARG user
ARG pass

USER root

# -z the length of STRING is zero
# [] are an alias for test command
# if $user is not empty, write credentials file
RUN if [ ! -z "$user" ]; then echo "${user} ${pass}">/root/proxy_credentials ; fi

#copy bash scripts
COPY configproxy.sh /root
COPY startup.sh .

RUN ["/bin/bash", "-c", ". /root/configproxy.sh"]

# Install dependencies and tools
#RUN apt-get update -y && \
#    apt-get install -yqq --no-install-recommends \
#    vim iputils-ping

ENTRYPOINT ["./startup.sh"]
CMD ["sh", "-c", "bash"]

Build without proxy settings

docker build -t img01 -f Dockerfile . 

Build with proxy settings

docker build -t img01 --build-arg user=<USER> --build-arg pass=<PASS> -f Dockerfile . 

Take a look here.

Siegfried
  • 90
  • 1
  • 7