Dockerfile if else condición con argumentos externos

131

Tengo dockerfile

FROM centos:7
ENV foo=42

luego lo construyo

docker build -t my_docker .

y ejecutarlo.

docker run -it -d  my_docker

¿Es posible pasar argumentos desde la línea de comando y usarlo con if else en Dockerfile? Me refiero a algo como

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

y construir con este argumento.

 docker build -t my_docker . --my_arg=42
nick_gabpe
fuente
1
Esto probablemente debería manejarse desde un script de compilación.
Snps
@ Зелёный eso es incorrecto. Vea la respuesta a continuación, esto se puede lograr con --build-arg
devnul3
La respuesta aceptada no cubre parte de la pregunta "condición si no". Sería mejor renombrarlo a "Dockerfile con argumentos externos" si la verificación de condición no significa que sea un requisito.
Ruslan Kabalin
@RuslanKabalin: la respuesta aceptada tiene cláusulas "entonces" y "más". La única diferencia es lo que se prueba en "condición si". Para el código mostrado en cuestión: RUN if [ "$arg" == "42" ]; then ENV=TRUE; else ENV=FALSE; fi. O si es posible que falte arg: RUN if [ "x$arg" == "x42" ]; then ...
ToolmakerSteve

Respuestas:

193

Puede que no se vea tan limpio, pero puede tener su Dockerfile (condicional) de la siguiente manera:

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

y luego construya la imagen como:

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

o

docker build -t my_docker .

Qasim Sarfraz
fuente
20
¿No debería ser en [ "$arg" = "x" ]lugar de [ "x$arg" = "x" ]?
Quannt
4
El ternario aquí es verificar si se proporciona algún argumento, en lugar de verificar un argumento específico. Parecería más evidente si reescrito como if [ $arg != "" ] ;, pero estoy seguro de que hay algunos Gotcha no estoy familiarizado con
myol
21
if [[ -n "$arg" ]]Es verdadero si un parámetro no está vacío, if [[ -z "$arg" ]]verdadero si un parámetro está vacío
acumartini
6
¿Cómo escribir una declaración if de varias líneas?
Ashutosh Chamoli
12
@Quannt - no, consulte ¿Por qué los scripts de shell ... [ "x$arg" = "x" ] parece que está comparando dos cadenas entre comillas, pero las comillas se eliminan; Esto mantiene la sintaxis correcta. Después de la cita de desmonte: Bueno: if x = x ... Malo: if = . SIN EMBARGO, HAY mejores formas de verificar la existencia de un parámetro: Verifique la existencia de argumentos de entrada .
ToolmakerSteve
20

Por alguna razón, la mayoría de las respuestas aquí no me ayudaron (tal vez esté relacionado con mi imagen FROM en el Dockerfile)

Así que preferí crear un bash scripten mi espacio de trabajo combinado con --build-argpara manejar la declaración if mientras Docker compila verificando si el argumento está vacío o no

Guión 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
....

Compilación de Docker:

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

Observación: esto irá al else (falso) en el script bash

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

Observación: esto irá al if (verdadero)

Edición 1:

Después de varios intentos he encontrado el siguiente artículo y esto uno que me ayudó a entender 2 cosas:

1) ARG antes de FROM está fuera de la compilación

2) El shell predeterminado es / bin / sh, lo que significa que if else está funcionando un poco diferente en la compilación de la ventana acoplable. por ejemplo, solo necesita un "=" en lugar de "==" para comparar cadenas.

Entonces puedes hacer esto dentro del Dockerfile

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

y en el docker build:

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

Existe una alternativa interesante a las soluciones propuestas, que funciona con un solo Dockerfile , requiere solo una llamada a la compilación de la ventana acoplable por compilación condicional y evita bash .

Solución:

Lo siguiente Dockerfileresuelve ese problema. Cópialo, pégalo y pruébalo tú mismo.

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

Explicación de Dockerfile:

Primero obtenemos una baseimagen ( centos:7en su caso) y la colocamos en su propio escenario. El baseescenario debe contener las cosas que desea hacer antes de la afección. Después de eso, tenemos dos etapas más, que representan las ramas de nuestra condición: branch-version-1y branch-version-2. Los construimos a ambos. La finaletapa que elige una de estas etapas, basándose en my_arg. Dockerfile condicional. Ahí tienes.

Salida cuando se ejecuta:

(Lo abrevié un poco ...)

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

¡Gracias a Tõnis por esta increíble idea!

Usuario12547645
fuente
Hasta ahora, el mejor enfoque.
Felipe Desiderati
También lo pensé, por eso lo publiqué aquí. Spead the news @FelipeDesiderati
User12547645
1
Esta es la mejor solución de todas las que se presentan aquí, porque no necesita usar soluciones alternativas con scripts / bash, sino usar una forma que solo involucre el conocimiento de Dockerfile. Gran respuesta.
Akito
¿Qué hace "ARG ENV"? ¿Puedes arrojar algo de luz sobre esto? Gracias.
Máximo
@Max gracias por señalar eso. No es necesario
User12547645
12

Simplemente use el binario de "prueba" directamente para hacer esto. También debe usar el comando noop ":" si no desea especificar una condición "else", para que la ventana acoplable no se detenga con un error de valor de retorno distinto de cero.

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
fuente
2
Igual que RUN [ -z "$YOURVAR" ] && ... || :, creo
OneCricketeer
4
Para la siguiente declaración, si se establece var, se ejecutan ambos echo. RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
Harshad Vyawahare
En efecto. Recuerde que esos no son bloques condicionales ramificados, se ejecutan en serie, se colocan &&delante de ||:test ! -z "$YOURVAR" && echo "var is set" || echo "var is not set"
Brandt
5

Exactamente como dijeron otros, el script de shell ayudaría.

Solo un caso adicional, en mi humilde opinión, vale la pena mencionarlo (para alguien más que se encuentre aquí, buscando un caso más fácil), que es el reemplazo del entorno .

Las variables de entorno (declaradas con la ENVdeclaración) también se pueden usar en ciertas instrucciones como variables para ser interpretadas por Dockerfile.

La ${variable_name}sintaxis también admite algunos de los modificadores de bash estándar que se especifican a continuación:

  • ${variable:-word}indica que si variablese establece, el resultado será ese valor. Si variableno se establece, wordserá el resultado.

  • ${variable:+word}indica que si variablese establece, entonces wordserá el resultado; de lo contrario, el resultado es la cadena vacía.

Jing Li
fuente
5

La respuesta aceptada puede resolver la pregunta, pero si desea ifcondiciones de varias líneas en el archivo docker, puede hacerlo colocando \al final de cada línea (similar a cómo lo haría en un script de shell) y terminando cada comando con ;. Incluso puede definir algo set -euxcomo el primer comando.

Ejemplo:

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

En tu caso:

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

Luego construye con:

docker build -t my_docker . --build-arg arg=42
Lucas Basquerotto
fuente
4

Usando el script Bash y 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

Pasando 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

Sin 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
fuente