Dockerfile if else condição com argumentos externos

129

Eu tenho dockerfile

FROM centos:7
ENV foo=42

então eu construo

docker build -t my_docker .

e execute-o.

docker run -it -d  my_docker

É possível passar argumentos da linha de comando e usá-los com if no Dockerfile? Quero dizer algo como

FROM centos:7
if (my_arg==42)
     {ENV=TRUE}
else:
     {ENV=FALSE}

e construa com este argumento.

 docker build -t my_docker . --my_arg=42
nick_gabpe
fonte
1
Isso provavelmente deve ser tratado a partir de um script de construção.
Snps de
@ Зелёный isso está incorreto. Veja a resposta abaixo, isso pode ser feito com --build-arg
devnul3
A resposta aceita não cobre a parte da pergunta "se outra condição". Seria melhor renomeá-lo para "Dockerfile com argumentos externos" se a verificação de condição não pretendesse ser um requisito.
Ruslan Kabalin
@RuslanKabalin - a resposta aceita contém as cláusulas "then" e "else". A única diferença é o que é testado em "condição se". Para código mostrado em questão: RUN if [ "$arg" == "42" ]; then ENV=TRUE; else ENV=FALSE; fi. Ou se arg estiver faltando: RUN if [ "x$arg" == "x42" ]; then ...
ToolmakerSteve

Respostas:

191

Pode não parecer tão limpo, mas você pode ter seu Dockerfile (condicional) da seguinte maneira:

FROM centos:7
ARG arg
RUN if [ "x$arg" = "x" ] ; then echo Argument not provided ; else echo Argument is $arg ; fi

e construir a imagem como:

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

ou

docker build -t my_docker .

Qasim Sarfraz
fonte
20
Não deveria ser em [ "$arg" = "x" ]vez de [ "x$arg" = "x" ]?
Quannt
4
O ternário aqui é verificar se algum argumento é fornecido, em vez de verificar um argumento específico. Pareceria mais óbvio se reescrito como, if [ $arg != "" ] ;mas tenho certeza de que há algum problema com o qual não estou familiarizado
myol
21
if [[ -n "$arg" ]]verdadeiro se um parâmetro não estiver vazio, if [[ -z "$arg" ]]verdadeiro se um parâmetro estiver vazio
acumartini
6
Como escrever uma declaração if multilinha?
Ashutosh Chamoli
12
@Quannt - não, consulte Por que scripts de shell ... [ "x$arg" = "x" ] parece que está comparando duas strings entre aspas, mas as aspas são removidas; Isso mantém a sintaxe correta. Depois de extracção Citação: Bom: if x = x ... Bad: if = . NO ENTANTO, HÁ maneiras melhores de verificar a existência de um parâmetro: Verifique a existência de argumentos de entrada .
ToolmakerSteve
20

Por algum motivo, a maioria das respostas aqui não me ajudou (talvez esteja relacionado à minha imagem FROM no Dockerfile)

Então, eu preferi criar um bash scriptem meu espaço de trabalho combinado com a --build-argfim de manipular a instrução if enquanto o Docker constrói verificando se o argumento está vazio ou não

Script 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

Dockerfile:

FROM ...
....
ARG arg
COPY bash.sh /tmp/  
RUN chmod u+x /tmp/bash.sh && /tmp/bash.sh $arg
....

Docker Build:

docker build --pull -f "Dockerfile" -t $SERVICE_NAME --build-arg arg="yes" .

Observação: Isso irá para o else (false) no script bash

docker build --pull -f "Dockerfile" -t $SERVICE_NAME .

Observação: Isso irá para o if (true)

Editar 1:

Depois de várias tentativas eu encontrei o seguinte artigo e este um que me ajudou a entender 2 coisas:

1) ARG antes de FROM estar fora da construção

2) O shell padrão é / bin / sh, o que significa que if else está funcionando um pouco diferente na construção do docker. por exemplo, você precisa apenas de um "=" em vez de "==" para comparar strings.

Então você pode fazer isso dentro do Dockerfile

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

e no docker build:

docker build --pull -f "Dockerfile" --label "service_name=${SERVICE_NAME}" -t $SERVICE_NAME --build-arg argname=true .
dsaydon
fonte
16

Há uma alternativa interessante para as soluções propostas, que funciona com um único Dockerfile , requer apenas uma única chamada para docker build por compilação condicional e evita bash .

Solução:

O seguinte Dockerfileresolve esse problema. Copie e cole e experimente você mesmo.

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}"

Explicação do Dockerfile:

Primeiro pegamos uma baseimagem ( centos:7no seu caso) e a colocamos em seu próprio palco. O basepalco deve conter coisas que você deseja fazer antes da condição. Depois disso, temos mais duas etapas, representando os ramos da nossa condição: branch-version-1e branch-version-2. Nós construímos os dois. O finalestágio então escolhe um desses estágios, com base em my_arg. Dockerfile condicional. Ai está.

Saída durante a execução:

(Eu abreviei um pouco isso ...)

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

my_var==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

Obrigado ao Tõnis por esta ideia incrível!

User12547645
fonte
Até agora, a melhor abordagem.
Felipe Desiderati
Também pensei assim, é por isso que o postei aqui. Espalhe a notícia @FelipeDesiderati
User12547645
1
Esta é a melhor solução de todas apresentadas aqui, porque você não precisa usar soluções alternativas com scripts / bash, mas use uma forma que envolva apenas conhecimento do Dockerfile. Ótima resposta.
Akito
O que "ARG ENV" faz? você pode lançar alguma luz sobre isso? Obrigado.
Máx.
@Max obrigado por apontar isso. Não é necessário
User12547645
12

Basta usar o binário "teste" diretamente para fazer isso. Você também deve usar o comando noop ":" se não quiser especificar uma condição "else", para que o docker não pare com um erro de valor de retorno diferente de zero.

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" && :
benjhess
fonte
2
O mesmo que RUN [ -z "$YOURVAR" ] && ... || :, eu acredito
OneCricketeer
4
Para a instrução a seguir, se var estiver definido, ambos os ecos serão executados. RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
Harshad Vyawahare
De fato. Lembre-se de que esses não são blocos condicionais ramificados, eles são executados em série, colocados &&na frente de ||:test ! -z "$YOURVAR" && echo "var is set" || echo "var is not set"
Brandt
5

Exatamente como outros disseram, o script de shell ajudaria.

Apenas um caso adicional, IMHO vale a pena mencionar (para alguém que tropeçar aqui, procurando um caso mais fácil), que é Substituição de ambiente .

Variáveis ​​de ambiente (declaradas com oENV instrução) também podem ser usadas em certas instruções como variáveis ​​a serem interpretadas pelo Dockerfile.

A ${variable_name}sintaxe também suporta alguns dos modificadores bash padrão, conforme especificado abaixo:

  • ${variable:-word}indica que se variableestiver definido, o resultado será esse valor. Se variablenão for definido, então wordserá o resultado.

  • ${variable:+word}indica que se variablefor definido, então wordserá o resultado, caso contrário, o resultado é a string vazia.

Jing Li
fonte
5

A resposta aceita pode resolver a questão, mas se quiser ifcondições de várias linhas no dockerfile, você pode fazer isso colocando \no final de cada linha (semelhante a como faria em um script de shell) e terminando cada comando com ;. Você pode até definir algo set -euxcomo o primeiro comando.

Exemplo:

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

No seu caso:

FROM centos:7
ARG arg
RUN if [ -z "$arg" ] ; then \
    echo Argument not provided; \
  else \
    echo Argument is $arg; \
  fi

Em seguida, construa com:

docker build -t my_docker . --build-arg arg=42
Lucas basquerotto
fonte
4

Usando script Bash e Alpine / Centos

Dockerfile

FROM alpine  #just change this to centos 

ARG MYARG=""
ENV E_MYARG=$MYARG

ADD . /tmp
RUN chmod +x /tmp/script.sh && /tmp/script.sh

script.sh

#!/usr/bin/env sh

if [ -z "$E_MYARG" ]; then
    echo "NO PARAM PASSED"
else
    echo $E_MYARG
fi

Passando arg: 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

Sem 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
....
Muhammad Soliman
fonte