在这种情况下应该使用ARG还是ENV?

198
这可能是一个简单的问题,但阅读ARGENV的文档并没有让我明白。
我正在构建一个PHP-FPM容器,我希望能够根据用户需要启用/禁用一些扩展。
如果能够在Dockerfile中添加条件语句并通过build命令传递标志来完成这个操作就非常好了,但据我所知不支持这样做。
对于我的情况和我的个人方法,我会在容器启动时运行一个小脚本,类似于以下内容:
#!/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 "$@"

这是我的 Dockerfile 的样子:
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

目前这个工作正常,但是如果我想添加例如20个扩展或者其他可以启用|禁用的功能,那么我将得到20个不必要的ENV定义(因为Dockerfile不支持.env文件),它们唯一的目的就是设置此标志以让脚本知道该做什么...

  • 这样做对吗?
  • 我应该使用ENV来实现这个目的吗?

如果您有不同的方法来实现这个,请告诉我,我很乐意接受建议。


1
如果这些扩展/功能在不同的构建之间不同,那么您应该使用ARG通过每个构建使用--build-arg设置不同的值来设置它们,并且您仍然可以在Dockerfile中使用默认值。如果您使用ENV,则需要编辑Dockerfile本身以设置不同的值进行每次构建。 - A.A.
请参阅 https://vsupalov.com/docker-arg-vs-env/。 - Michael Freidgeim
4个回答

356

来自Dockerfile参考文档:

  • ARG指令定义了一个变量,用户可以使用 --build-arg <varname>=<value> 标志在构建时通过 docker build 命令传递给构建器。

  • ENV指令将环境变量 <key>设置为值 <value>
    使用ENV设置的环境变量将在从生成的镜像运行容器时保持不变。

如果需要进行构建时定制,则ARG是最佳选择。
如果需要进行运行时定制(以使用不同的设置运行相同的映像),则ENV非常适合。

如果我想添加例如20(一种随机数字)个扩展或任何其他可启用|禁用的功能

考虑到所涉及的组合数,最好使用ENV在运行时设置这些功能。

但是您可以结合使用两者:

  • 使用特定的ARG构建镜像
  • 将该ARG用作ENV

也就是说,使用包含以下内容的Dockerfile:

ARG var
ENV var=${var}

然后你可以选择在构建时使用特定的var值创建镜像(docker build --build-arg var=xxx),或者在运行时使用特定的值运行容器(docker run -e var=yyy


1
很好,但是这些ARG可以从我在容器启动时运行的脚本中访问吗?如果可以,怎么做?您能否通过添加一个小例子来改进您的答案,说明如何从bash脚本中访问它们? - ReynierPM
1
@ReynierPM,你可以在Dockerfile(构建时)中声明一个ENV var=${var},除了使用ARG之外,参见https://dev59.com/UlsX5IYBdhLWcg3wW-Q7#33936014。两者都要使用。 - VonC
@VonC 好的,这是我从你和BMitck那里得到的信息:无论如何都需要ENV,但如果我需要在运行构建之前覆盖某些值,则可以与ARG结合使用以避免编辑Dockerfile,我现在是对的吗? - ReynierPM
@VonC,ARGENV中哪一个会为Docker镜像添加额外的层? - Hardeep Singh
1
@HardeepSingh 两者都可以使用:ENV(https://dev59.com/W10Z5IYBdhLWcg3wpRpW#33836848)和ARG(https://dev59.com/bVgR5IYBdhLWcg3wM7Hi#41593407)。 - VonC
显示剩余3条评论

6
一个额外的警告:如果您在FROM子句之前和之后使用ARG默认值,则需要重复定义。因此,不要这样做。
ARG var1=default1
FROM your_base:${var1}
ENV var1=${var1}  # <- this will fail!; the ARG var1 default is empty

你需要

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

请注意,您必须手动/显式地设置默认值(例如示例中的default1),使其两次的值相同。您可以在FROM语句之前/之后为ARG变量设置两个不同的默认值。

4

如果想要为每个构建设置不同的环境变量值,我们可以在构建时传递这些值,而无需每次更改Docker文件。

然而,ENV设置后就不能通过命令行值进行覆盖。因此,如果我们希望环境变量在不同的构建中具有不同的值,可以使用ARG并在Docker文件中设置默认值。当我们想要覆盖这些值时,可以在每次构建时使用--build-args,而无需更改Docker文件。

更多详情,请参考这里


2

为什么要使用 ARGENV

假设我们有一个 jar 文件,并且我们想将其制作成 docker 镜像。因此,我们可以将其传递给任何docker引擎。

我们可以编写一个 Dockerfile

Dockerfile

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

现在,如果我们想要使用Maven构建Docker镜像,我们可以使用--build-arg传递JAR_FILE,例如:target/*.jar。
docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp 

然而,如果我们使用的是Gradle;上述命令无法工作,我们必须传递不同的路径:build/libs/

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

一旦选择了构建系统,我们就不需要ARG了。可以将JAR位置进行硬编码。
对于Maven而言,如下所示:
Dockerfile
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

在这里,我们可以使用以下命令构建图像:

docker build -t image:tag .

何时使用`ENV`?

如果我们想要在运行容器时设置一些值,并将其反映到镜像中,例如应用程序可以运行/侦听的端口号。我们可以使用ENV来设置。

ARGENV都非常相似。两者都可以以相同的方式从我们的Dockerfile命令中访问。

示例:

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

个人选择!

在选择ARG和ENV之间存在权衡。如果选择ARG,则无法在运行期间更改它。但是,如果选择ENV,则可以在容器中修改该值。

我个人偏爱在任何可以使用的情况下使用ARG而不是ENV,例如:

在上面的示例中:

我已经使用ARG作为构建系统mavenGradle在构建期间影响,而不是在运行时。因此,它封装了很多细节,并为运行时提供了最少的参数。

有关更多详细信息,请参阅this


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接