ARG or ENV, which one to use in this case?
Asked Answered
D

4

211

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

Disillusion answered 29/1, 2017 at 0:24 Comment(2)
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 valuesGeisha
See also vsupalov.com/docker-arg-vs-envThriftless
F
372

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)

Fisticuffs answered 29/1, 2017 at 8:50 Comment(8)
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?Disillusion
@Disillusion you can, by declaring in your Dockerfile (build time), in addition of ARG, an ENV var=${var}: see https://mcmap.net/q/93111/-how-to-define-a-variable-in-a-dockerfile. Use both.Fisticuffs
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.Disillusion
@Disillusion OK. See https://mcmap.net/q/93111/-how-to-define-a-variable-in-a-dockerfile. Add ARG and ENV as mentioned in that answer.Fisticuffs
@Fisticuffs 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?Disillusion
@Disillusion Yes, that is pretty much the idea.Fisticuffs
@Fisticuffs which out of ARG and ENV add an extra layer to docker image?Pieria
@HardeepSingh Both: ENV (https://mcmap.net/q/108154/-what-are-docker-image-quot-layers-quot) and ARG (https://mcmap.net/q/128784/-does-putting-arg-at-top-of-dockerfile-prevent-layer-re-use)Fisticuffs
E
7

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.

Enlargement answered 14/3, 2023 at 10:14 Comment(0)
F
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.

Filide answered 16/9, 2019 at 5:35 Comment(0)
R
4

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.

Raina answered 27/12, 2022 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.