Dockerfile if else条件与外部参数

nic*_*bpe 86 docker dockerfile

我有dockerfile

FROM centos:7
ENV foo=42
Run Code Online (Sandbox Code Playgroud)

然后我建立它

docker build -t my_docker .
Run Code Online (Sandbox Code Playgroud)

并运行它.

docker run -it -d  my_docker
Run Code Online (Sandbox Code Playgroud)

是否可以从命令行传递参数并在Dockerfile中使用if else?我的意思是

FROM centos:7
if (my_arg==42)
     {ENV=TRUE}
else:
     {ENV=FALSE}
Run Code Online (Sandbox Code Playgroud)

并使用此参数构建.

 docker build -t my_docker . --my_arg=42
Run Code Online (Sandbox Code Playgroud)

Qas*_*raz 141

它可能看起来不干净但你可以拥有你的Dockerfile(有条件的)如下:

FROM centos:7
ARG arg
RUN if [ "x$arg" = "x" ] ; then echo Argument not provided ; else echo Argument is $arg ; fi
Run Code Online (Sandbox Code Playgroud)

然后将图像构建为:

docker build -t my_docker . --build-arg arg=45

要么

docker build -t my_docker .

  • 不应该是`["$ arg"="x"]`而不是`["x $ arg"="x"]`? (15认同)
  • `if [[-n"$ arg"]]`如果param不为空则为true,`if [[-z"$ arg"]]`如果param为空则为true (12认同)
  • 请注意,由于默认 shell 的差异,基于 Debian 的镜像需要 `[`, `]` 而不是 `[[`, `]]`(如果 Alpine 使用 `[[`, `]]`,似乎没有问题)。有关详细信息,请参阅 [答案:if 语句 - 在 Bash 中双方括号 \[\[ \]\] 比单方括号 \[ \] 更好吗?- 堆栈溢出](/sf/answers/46864051/) (5认同)
  • 这里的三元组是检查是否提供了任何参数,而不是检查特定的参数.如果重写为"if [$ arg!=""],这似乎更明显;但是我确定有一些问题我不熟悉 (4认同)
  • 如何编写多行if语句? (4认同)
  • @Quannt-不,请参见[为什么要执行Shell脚本...](/sf/ask/12188361/)`[“ x $ arg” =“ x”]`看起来像是在比较两个引用的引号字符串,但引号被删除;这样可以使语法正确。引号后:好:`如果x = x ..`。错误:`if =`。但是,有更好的方法来检查参数的存在:[检查输入参数的存在](/sf/ask/453766421/)。 (4认同)
  • @RichardDunn 在技术上不正确,但在这里也并不真正相关。ARG 是一个可以位于 FROM 之前的语句,用于非常特定的目的。请参阅[文档](https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact)。您可以使用它来设置基础映像的版本。 (3认同)
  • 在 Docker 中使用这种方法是否被认为是好的做法? (2认同)

Use*_*645 112

所提出的解决方案有一个有趣的替代方案,它适用于单个 Dockerfile,每个条件构建只需要一次调用docker build 并避免 bash

解决方案:

下面Dockerfile解决这个问题。复制粘贴它并自己尝试。

ARG my_arg

FROM centos:7 AS base
RUN echo "do stuff with the centos image"

FROM base AS branch-version-1
RUN echo "this is the stage that sets VAR=TRUE"
ENV VAR=TRUE

FROM base AS branch-version-2
RUN echo "this is the stage that sets VAR=FALSE"
ENV VAR=FALSE

FROM branch-version-${my_arg} AS final
RUN echo "VAR is equal to ${VAR}"
Run Code Online (Sandbox Code Playgroud)

Dockerfile 说明:

我们首先获得一个base图像(centos:7在您的情况下)并将其放入自己的舞台。该base阶段应包含所需的条件之前做的事情。之后,我们还有两个阶段,代表我们条件的分支:branch-version-1branch-version-2。我们构建了它们。final阶段然后根据选择这些阶段之一my_arg。有条件的 Dockerfile。你去吧。

运行时输出:

(我把这个缩写了一点...)

my_arg==2

docker build --build-arg my_arg=2 .
Step 1/12 : ARG my_arg
Step 2/12 : ARG ENV
Step 3/12 : FROM centos:7 AS base
Step 4/12 : RUN echo "do stuff with the centos image"
do stuff with the centos image
Step 5/12 : FROM base AS branch-version-1
Step 6/12 : RUN echo "this is the stage that sets VAR=TRUE"
this is the stage that sets VAR=TRUE
Step 7/12 : ENV VAR=TRUE
Step 8/12 : FROM base AS branch-version-2
Step 9/12 : RUN echo "this is the stage that sets VAR=FALSE"
this is the stage that sets VAR=FALSE
Step 10/12 : ENV VAR=FALSE
Step 11/12 : FROM branch-version-${my_arg}
Step 12/12 : RUN echo "VAR is equal to ${VAR}"
VAR is equal to FALSE
Run Code Online (Sandbox Code Playgroud)

my_arg==1

docker build --build-arg my_arg=1 .
...
Step 11/12 : FROM branch-version-${my_arg}
Step 12/12 : RUN echo "VAR is equal to ${VAR}"
VAR is equal to TRUE
Run Code Online (Sandbox Code Playgroud)

感谢 Tõnis 这个惊人的想法!

  • 到目前为止,最好的方法。 (7认同)
  • @Vitaliy Docker 在 18.09+ 版本中支持 BuildKit(适用于基于 Linux 的容器),这将跳过执行未使用阶段的构建。`DOCKER_BUILDKIT=1 docker 构建。` (6认同)
  • 有趣的想法!我在一般情况下看到这种方法的缺点是 if 语句的两个分支都会被执行,这可能成本高昂/有副作用。 (5认同)
  • 优秀的解决方案,只需记住在第一个 FROM 语句之前添加 ARG,因为它是有作用域的。如果将其添加在 FROM 语句之后,则分支 FROM 将无法识别它。另外,请记住,ARG 都是有作用域的,如果您在分支中定义 ARG,它们在最终构建中将不可用 - 请改用 ENV。 (5认同)
  • 这是这里提出的所有解决方案中最好的解决方案,因为您不需要使用 script/bash 的解决方法,而是使用仅涉及 Dockerfile 知识的方法。很好的答案。 (2认同)

bar*_*rat 18

根据build命令文档,有一个名为的参数--build-arg

https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg

用法示例
docker build --build-arg HTTP_PROXY=http://10.20.30.2:1234 .

IMO这是你需要的:)


Luc*_*tto 15

接受的答案可以解决问题的,但如果你想多if在dockerfile的条件下,你可以做到这一点放置\在每行的末尾(类似于你将如何在shell脚本做),并结束与每个命令;。您甚至可以将类似的内容定义set -eux为第一个命令。

例子:

RUN set -eux; \
  if [ -f /path/to/file ]; then \
    mv /path/to/file /dest; \
  fi; \
  if [ -d /path/to/dir ]; then \
    mv /path/to/dir /dest; \
  fi
Run Code Online (Sandbox Code Playgroud)

在你的情况下:

FROM centos:7
ARG arg
RUN if [ -z "$arg" ] ; then \
    echo Argument not provided; \
  else \
    echo Argument is $arg; \
  fi
Run Code Online (Sandbox Code Playgroud)

然后构建:

docker build -t my_docker . --build-arg arg=42
Run Code Online (Sandbox Code Playgroud)


dsa*_*don 12

由于某种原因,这里的大多数答案都没有帮助我(也许它与我在Dockerfile中的FROM图像有关)

所以我更喜欢bash script在我的工作空间中创建一个结合,--build-arg以便在Docker构建时通过检查参数是否为空来处理if语句

Bash脚本:

#!/bin/bash -x

if test -z $1 ; then 
    echo "The arg is empty"
    ....do something....
else 
    echo "The arg is not empty: $1"
    ....do something else....
fi
Run Code Online (Sandbox Code Playgroud)

Dockerfile:

FROM ...
....
ARG arg
COPY bash.sh /tmp/  
RUN chmod u+x /tmp/bash.sh && /tmp/bash.sh $arg
....
Run Code Online (Sandbox Code Playgroud)

Docker构建:

docker build --pull -f "Dockerfile" -t $SERVICE_NAME --build-arg arg="yes" .
Run Code Online (Sandbox Code Playgroud)

备注:这将转到bash脚本中的else(false)

docker build --pull -f "Dockerfile" -t $SERVICE_NAME .
Run Code Online (Sandbox Code Playgroud)

备注:这将转到if(true)

编辑1:

几次尝试之后,我发现下面的文章,这一个 这让我明白两两件事:

1)FROM之前的ARG在构建之外

2)默认的shell是/ bin/sh,这意味着在docker构建中if else的工作方式有点不同.例如,您只需要一个"="而不是"=="来比较字符串.

所以你可以在里面做到这一点 Dockerfile

ARG argname=false   #default argument when not provided in the --build-arg
RUN if [ "$argname" = "false" ] ; then echo 'false'; else echo 'true'; fi
Run Code Online (Sandbox Code Playgroud)

并在docker build:

docker build --pull -f "Dockerfile" --label "service_name=${SERVICE_NAME}" -t $SERVICE_NAME --build-arg argname=true .
Run Code Online (Sandbox Code Playgroud)


Dav*_*ave 12

尽可能不要使用其他答案中描述的构建参数。这是一个旧的混乱解决方案。Docker 的 target 属性解决了这个问题。

目标示例

文件

FROM foo as base

RUN ...

# Build dev image
FROM base as image-dev

RUN ...
COPY ...

# Build prod image
FROM base as image-prod

RUN ...
COPY ...
Run Code Online (Sandbox Code Playgroud)
docker build --target image-dev -t foo .
Run Code Online (Sandbox Code Playgroud)
docker build --target image-dev -t foo .
Run Code Online (Sandbox Code Playgroud)

真实世界

Dockerfiles 在现实世界中变得复杂。使用 buildkit &COPY --from来获得更快、更易于维护的 Dockerfile:

  • Docker 构建目标之上的每个阶段,无论它是否被继承。使用 buildkit 仅构建继承的阶段。Docker 必须通过 v19+。希望这将很快成为默认功能。
  • 目标可以共享构建阶段。使用COPY --from以简化继承。
version: '3.4'

services:

  dev:
    build:
      context: .
      dockerfile: Dockerfile
      target: image-dev
Run Code Online (Sandbox Code Playgroud)

启用实验模式。

sudo echo '{"experimental": true}' | sudo tee /etc/docker/daemon.json
Run Code Online (Sandbox Code Playgroud)

在启用 buildkit 的情况下构建。默认情况下,Buildkit 在没有缓存的情况下构建 - 启用--build-arg BUILDKIT_INLINE_CACHE=1

CI 构建工作。

DOCKER_BUILDKIT=1 \
    docker build \
    --build-arg BUILDKIT_INLINE_CACHE=1 \
    --target image-ci\
    -t foo:ci
    .
Run Code Online (Sandbox Code Playgroud)

使用来自拉取图像的缓存 --cache-from

产品构建工作

docker pull foo:ci
docker pull foo:stage

DOCKER_BUILDKIT=1 \
    docker build \
    --cache-from foo:ci,foo:stage \
    --target image-dev \
    -t prod
    .
Run Code Online (Sandbox Code Playgroud)

  • 这应该是现在公认的答案。 (9认同)
  • 这里有一些非常有用的想法,非常感谢! (4认同)
  • 谢谢!这很有帮助!其他答案侧重于使用参数来执行 bash 命令 - 这是唯一允许您在“条件”为真时执行 docker 命令的答案(目标设置为您的环境) (3认同)
  • 通过这种模式,我可以在普通容器中拥有“USER <unprivileged>”,在 -debug 容器中拥有“USER root”,这对我来说很有用,并且比使用 bash if/else 方法更容易实现。感谢这个模式! (2认同)
  • @mike01010 在 Docker v23 中默认启用。 (2认同)

小智 11

只需直接使用"test"二进制文件即可.如果您不想指定"else"条件,也应该使用noop命令":",因此docker不会因非零返回值错误而停止.

RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
RUN test -z "$YOURVAR" && echo "var is not set" || :
RUN test -z "$YOURVAR" || echo "var is set" && :
Run Code Online (Sandbox Code Playgroud)

  • 对于以下语句,如果设置了 var,则两个 echo 都会被执行。`运行测试-z“$YOURVAR”|| echo "var 已设置" && echo "var 未设置"` (4认同)
  • 与 `RUN [ -z "$YOURVAR" ] && ... || 相同 :`,我相信 (2认同)

Jin*_* Li 8

正如其他人所说,shell 脚本会有所帮助。

只是一个额外的案例,恕我直言,值得一提(对于在这里偶然发现的其他人,寻找一个更简单的案例),那就是Environment replacement

环境变量(用ENV语句声明)也可以在某些指令中用作要由Dockerfile.

${variable_name}语法还支持以下指定的一些标准 bash 修饰符:

  • ${variable:-word}表示如果variable设置,则结果将是该值。如果variable未设置,word则将是结果。

  • ${variable:+word}表示如果variable设置则为word结果,否则为空字符串。


Muh*_*man 5

使用 Bash 脚本和 Alpine/Centos

文件

FROM alpine  #just change this to centos 

ARG MYARG=""
ENV E_MYARG=$MYARG

ADD . /tmp
RUN chmod +x /tmp/script.sh && /tmp/script.sh
Run Code Online (Sandbox Code Playgroud)

脚本文件

#!/usr/bin/env sh

if [ -z "$E_MYARG" ]; then
    echo "NO PARAM PASSED"
else
    echo $E_MYARG
fi
Run Code Online (Sandbox Code Playgroud)

传递参数: docker build -t test --build-arg MYARG="this is a test" .

....
Step 5/5 : RUN chmod +x /tmp/script.sh && /tmp/script.sh
 ---> Running in 10b0e07e33fc
this is a test
Removing intermediate container 10b0e07e33fc
 ---> f6f085ffb284
Successfully built f6f085ffb284
Run Code Online (Sandbox Code Playgroud)

没有 arg: docker build -t test .

....
Step 5/5 : RUN chmod +x /tmp/script.sh && /tmp/script.sh
 ---> Running in b89210b0cac0
NO PARAM PASSED
Removing intermediate container b89210b0cac0
....
Run Code Online (Sandbox Code Playgroud)


小智 5

# The ARGs in front of FROM is for image
ARG IMLABEL=xxxx \
    IMVERS=x.x

FROM ${IMLABEL}:${IMVERS}

# The ARGs after FROM is for parameters to be used in the script
ARG  condition-x

RUN if [ "$condition-x" = "condition-1" ]; then \
      echo "$condition-1"; \
    elif [ "$condition-x" = "condition-1" ]; then \
      echo "$condition-2"; \
    else
      echo "$condition-others"; \
    fi
Run Code Online (Sandbox Code Playgroud)
build -t --build-arg IMLABEL --build-arg IMVERS --build-arg condition-x -f Dockerfile -t image:version .
Run Code Online (Sandbox Code Playgroud)