195

This could be maybe a trivial question but reading docs for ARG and ENV doesn't put things clear to me.

I am building a PHP-FPM container and I want to give the ability for enable/disable some extensions on user needs.

Would be great if this could be done in the Dockerfile by adding conditionals and passing flags on the build command perhaps but AFAIK is not supported.

In my case and my personal approach is to run a small script when container starts, something like the following:

#!/bin/sh   
set -e

RESTART="false"

# This script will be placed in /config/init/ and run when container starts.
if  [ "$INSTALL_XDEBUG" == "true" ]; then
    printf "\nInstalling Xdebug ...\n"
    yum install -y  php71-php-pecl-xdebug
    RESTART="true"
fi
...   
if  [ "$RESTART" == "true" ]; then
    printf "\nRestarting php-fpm ...\n"
    supervisorctl restart php-fpm
fi

exec "$@"

This is how my Dockerfile looks like:

FROM reynierpm/centos7-supervisor
ENV TERM=xterm \
    PATH="/root/.composer/vendor/bin:${PATH}" \
    INSTALL_COMPOSER="false" \
    COMPOSER_ALLOW_SUPERUSER=1 \
    COMPOSER_ALLOW_XDEBUG=1 \
    COMPOSER_DISABLE_XDEBUG_WARN=1 \
    COMPOSER_HOME="/root/.composer" \
    COMPOSER_CACHE_DIR="/root/.composer/cache" \
    SYMFONY_INSTALLER="false" \
    SYMFONY_PROJECT="false" \
    INSTALL_XDEBUG="false" \
    INSTALL_MONGO="false" \
    INSTALL_REDIS="false" \
    INSTALL_HTTP_REQUEST="false" \
    INSTALL_UPLOAD_PROGRESS="false" \
    INSTALL_XATTR="false"

RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
                   https://rpms.remirepo.net/enterprise/remi-release-7.rpm
RUN yum install -y  \
        yum-utils \
        git \
        zip \
        unzip \
        nano \
        wget \
        php71-php-fpm \
        php71-php-cli \
        php71-php-common \
        php71-php-gd \
        php71-php-intl \
        php71-php-json \
        php71-php-mbstring \
        php71-php-mcrypt \
        php71-php-mysqlnd \
        php71-php-pdo \
        php71-php-pear \
        php71-php-xml \
        php71-pecl-apcu \
        php71-php-pecl-apfd \
        php71-php-pecl-memcache \
        php71-php-pecl-memcached \
        php71-php-pecl-zip && \
        yum clean all && rm -rf /tmp/yum*

RUN ln -sfF /opt/remi/php71/enable /etc/profile.d/php71-paths.sh && \
    ln -sfF /opt/remi/php71/root/usr/bin/{pear,pecl,phar,php,php-cgi,phpize} /usr/local/bin/. && \
    mv -f /etc/opt/remi/php71/php.ini /etc/php.ini && \
    ln -s /etc/php.ini /etc/opt/remi/php71/php.ini && \
    rm -rf /etc/php.d && \
    mv /etc/opt/remi/php71/php.d /etc/. && \
    ln -s /etc/php.d /etc/opt/remi/php71/php.d

COPY container-files /
RUN chmod +x /config/bootstrap.sh
WORKDIR /data/www
EXPOSE 9001

Currently this is working but ... If I want to add let's say 20 (a random number) of extensions or any other feature that can be enable|disable then I will end with 20 non necessary ENV (because Dockerfile doesn't support .env files) definition whose only purpose would be set this flag for let the script knows what to do then ...

  • Is this the right way to do it?
  • Should I use ENV for this purpose?

I am open to ideas if you have a different approach for achieve this please let me know about it

uladzimir
  • 5,639
  • 6
  • 31
  • 50
ReynierPM
  • 17,594
  • 53
  • 193
  • 363
  • 1
    If those extensions/features would be different from one build to another, then you should use `ARG` to set them with different values with each build using `--build-arg`, and you can still use default values in the Dockerfile. If you use `ENV`, you would need to edit the Dockerfile itself for every build to set different values – A.A. Jan 29 '17 at 04:16
  • See also https://vsupalov.com/docker-arg-vs-env/ – Michael Freidgeim Nov 28 '20 at 20:24

4 Answers4

347

From Dockerfile reference:

  • The ARG instruction defines a variable that users can pass at build-time to the builder with the docker build command using the --build-arg <varname>=<value> flag.

  • The ENV instruction sets the environment variable <key> to the value <value>.
    The environment variables set using ENV will persist when a container is run from the resulting image.

So if you need build-time customization, ARG is your best choice.
If you need run-time customization (to run the same image with different settings), ENV is well-suited.

If I want to add let's say 20 (a random number) of extensions or any other feature that can be enable|disable

Given the number of combinations involved, using ENV to set those features at runtime is best here.

But you can combine both by:

  • building an image with a specific ARG
  • using that ARG as an ENV

That is, with a Dockerfile including:

ARG var
ENV var=${var}

You can then either build an image with a specific var value at build-time (docker build --build-arg var=xxx), or run a container with a specific runtime value (docker run -e var=yyy)

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Great but those `ARG` can be accessed from the script I am running on container startup? If so how? Could you improve your answer by adding a little example about how those can be accessed from a bash script? – ReynierPM Jan 29 '17 at 15:04
  • 1
    @ReynierPM you can, by declaring in your Dockerfile (build time), in *addition* of `ARG`, an `ENV var=${var}`: see http://stackoverflow.com/a/33936014/6309. Use both. – VonC Jan 29 '17 at 16:32
  • If I use your approach then no matter what I will end up with a `var` ENV variable on the container when it start, I am right? Otherwise I'm not following you at all. Remember this: the script gets copied from a local folder to the container and it's used upon container initialization, that's why I am using ENV instead of ARG because I don't know if when container start the ARG still alive and can be accessed from inside a bash script. – ReynierPM Jan 29 '17 at 17:30
  • @ReynierPM OK. See http://stackoverflow.com/a/33936014/6309. Add `ARG` and `ENV` as mentioned in that answer. – VonC Jan 29 '17 at 17:44
  • @VonC ok, this is what I got from you and BMitck: `ENV` is needed not matter what but I can combine with `ARG` in order to non edit the `Dockerfile` if I need to override some values before I run the build, I'm right now? – ReynierPM Jan 29 '17 at 22:59
  • @ReynierPM Yes, that is pretty much the idea. – VonC Jan 29 '17 at 23:00
  • @VonC which out of `ARG` and `ENV` add an extra layer to docker image? – Hardeep Singh Aug 08 '18 at 07:26
  • 1
    @HardeepSingh Both: ENV (https://stackoverflow.com/a/33836848/6309) and ARG (https://stackoverflow.com/a/41593407/6309) – VonC Aug 08 '18 at 07:29
4

So if want to set the value of an environment variable to something different for every build then we can pass these values during build time and we don't need to change our docker file every time.

While ENV, once set cannot be overwritten through command line values. So, if we want to have our environment variable to have different values for different builds then we could use ARG and set default values in our docker file. And when we want to overwrite these values then we can do so using --build-args at every build without changing our docker file.

For more details, you can refer this.

user2719152
  • 939
  • 3
  • 11
  • 20
4

One additional tripwire: ARG default values need duplicated definitions if you use them before and below the FROM clause. So instead of

ARG var1=default1
FROM your_base:${var1}
ENV var1=${var1}  # <- this will fail!; the ARG var1 default is empty

you need

ARG var1=default1
FROM your_base:${var1}
ARG var1=default1
ENV var1=${var1}  # <- this works

Note that you have to manually/explicitly set the defaults (default1 in the example) to be the same value both times. You could set two different defaults for the ARG variable before/after the FROM statement.

Oliver Zendel
  • 2,695
  • 34
  • 29
2

Why to use ARG or ENV ?

Let's say we have a jar file and we want to make a docker image of it. So, we can ship it to any docker engine.

We can write a Dockerfile.

Dockerfile

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Now, if we want to build the docker image using Maven, we can pass the JAR_FILE using the --build-arg as target/*.jar

docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp 

However, if we are using Gradle; the above command doesn't work and we've to pass a different path: build/libs/

docker build --build-arg JAR_FILE=build/libs/*.jar -t myorg/myapp .

Once you have chosen a build system, we don’t need the ARG. We can hard code the JAR location.

For Maven, that would be as follows:

Dockerfile

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

here, we can build an image with the following command:

docker build -t image:tag .

When to use `ENV`?

If we want to set some values at running containers and reflect that to the image like the Port Number that your application can run/listen on. We can set that using the ENV.

Both ARG and ENV seem very similar. Both can be accessed from within our Dockerfile commands in the same manner.

Example:

ARG VAR_A 5
ENV VAR_B 6
RUN echo $VAR_A
RUN echo $VAR_B

Personal Option!

There is a tradeoff between choosing ARG over ENV. If you choose ARG you can't change it later during the run. However, if you chose ENV you can modify the value at the container.

I personally prefer ARG over ENV wherever I can, like,

In the above Example:

I have used ARG as the build system maven or Gradle impacts during build rather than runtime. It thus encapsulates a lot of details and provided a minimum set of arguments for the runtime.

For more details, you can refer to this.

Zahid Khan
  • 2,130
  • 2
  • 18
  • 31