¿COPIA / AÑADIR condicional en Dockerfile?

103

Dentro de mis Dockerfiles, me gustaría COPIAR un archivo en mi imagen si existe, el archivo requirements.txt para pip parece un buen candidato, pero ¿cómo se lograría?

COPY (requirements.txt if test -e requirements.txt; fi) /destination
...
RUN  if test -e requirements.txt; then pip install -r requirements.txt; fi

o

if test -e requirements.txt; then
    COPY requiements.txt /destination;
fi
RUN  if test -e requirements.txt; then pip install -r requirements.txt; fi
derrend
fuente
Consulte aquí: docs.docker.com/reference/builder
Tuan
4
@Tuan - ¿Qué ayuda específicamente en ese enlace a hacer esto?
ToolmakerSteve

Respuestas:

24

Actualmente, esto no es compatible (ya que sospecho que conduciría a una imagen no reproducible, ya que el mismo Dockerfile copiaría o no el archivo, dependiendo de su existencia).

Esto todavía se solicita, en el número 13045 , utilizando comodines: " COPY foo/* bar/" not work if no file in foo" (mayo de 2015).
No se implementará por ahora (julio de 2015) en Docker, pero otra herramienta de compilación como bocker podría admitir esto.

VonC
fuente
32
buena respuesta, pero la lógica de la ventana acoplable, en mi opinión, es defectuosa. si ejecuta el mismo archivo docker con un contexto de compilación diferente, obtendrá una imagen diferente. eso es de esperar. usar el mismo contexto de construcción dará la misma imagen. y si inserta instrucciones COPY / ADD condicionales en el mismo contexto de compilación, obtendrá la misma imagen. de modo que compruebe. Eso es solo mis 2 centavos.
nathan g
Docker se trata de una infraestructura inmutable. El desarrollo, la puesta en escena y la producción de sus entornos deben ser 99,99% lo más cerca posible, si no idénticos. Utilice variables de entorno.
AndrewMcLagan
3
@AndrewMcLagan, ¿qué pasa si, por ejemplo, un deventorno de front-end se ejecuta con un servidor de desarrollo webpack y el prodentorno equivalente funciona con una /distcarpeta estática? Este es el caso en la actualidad la mayoría de front-end configuraciones, y, obviamente, devy prodno puede ser lo mismo aquí. Entonces, ¿cómo lidiar con eso?
Jivan
No uso Docker para desarrollar las interfaces de mi nodo. El localhost del paquete web normal: 3000, etc ... Aunque aún inicie su entorno de desarrollo de la ventana acoplable local, por lo que su interfaz de nodo / reacción / angular se comunica con lo que se esté ejecutando en su entorno de contenedor de ventana acoplable normal. Por ejemplo, API, redis, MySQL, mongo, búsqueda elástica y cualquier otro micro servicio. Usted ... podría ... ejecutar un entorno de desarrollo de paquete web en un contenedor. Pero siento que está demasiado lejos ...
AndrewMcLagan
@Jivan ¿Qué tal usar una imagen incorporada para definir las instrucciones comunes y luego construir imágenes específicas para desarrollo y producción? El repositorio de nodo de Docker Hub parece contener imágenes integradas para cada versión de nodo: hub.docker.com/_/node . O tal vez podrías enrollar el tuyo.
david_i_smith
83

Aquí hay una solución alternativa simple:

COPY foo file-which-may-exist* /target

Asegúrese de que fooexista, ya que COPYnecesita al menos una fuente válida.

Si file-which-may-existestá presente, también se copiará.

NOTA: Debe asegurarse de que su comodín no recoja otros archivos que no tenga la intención de copiar. Para tener más cuidado, puede usar file-which-may-exist?en su lugar ( ?coincide con un solo carácter).

O incluso mejor, use una clase de carácter como esta para asegurarse de que solo se pueda hacer coincidir un archivo:

COPY foo file-which-may-exis[t] /target
jdhildeb
fuente
1
¿Puedes hacer lo mismo con una carpeta?
Benjamin Toueg
1
@BenjaminToueg: Sí, de acuerdo con los documentos , puede copiar archivos y carpetas.
jdhildeb
2
Esto funciona muy bien. Para archivos con múltiples destinos, los copié en un directorio temporal y luego los moví a donde los necesitaba. COPY --from=docker /usr/bin/docker /usr/lib/libltdl.so* /tmp/docker/ RUN mv /tmp/docker/docker /usr/bin/docker RUN mv /tmp/docker/libltdl.so.7 /usr/lib/libltdl.so.7 || true(donde la biblioteca compartida es la entidad desconocida.)
Adam K Dean
Al copiar varios archivos existentes, el destino debe ser un directorio. ¿Cómo funciona esto cuando tanto foo como su archivo-que-puede-existir * existen?
melchoir55
1
Entonces, la respuesta es 'asegúrese de que haya un archivo' y luego una demostración sobre cómo usar el operador COPY. No veo cómo esto se relaciona con la pregunta original.
derrend
27

Como se indica en este comentario , la respuesta de Santhosh Hirekerur aún copia el archivo, para archivar una copia condicional verdadera, puede usar este método.

ARG BUILD_ENV=copy

FROM alpine as build_copy
ONBUILD COPY file /file

FROM alpine as build_no_copy
ONBUILD RUN echo "I don't copy"

FROM build_${BUILD_ENV}
# other stuff

Las ONBUILDinstrucciones aseguran que el archivo solo se copia si la "rama" es seleccionada por el BUILD_ENV. Configura esta var usando un pequeño script antes de llamardocker build

Siyu
fuente
2
Me gusta esta respuesta porque me abrió los ojos no solo a ONBUILD, que es muy útil, sino que también parece la más fácil de integrar con otras variables pasadas, por ejemplo, si desea establecer la etiqueta basada en BUILD_ENV, o almacenar algún estado en ENV.
DeusXMachina
Intenté algo así y obtuve: Respuesta de error del demonio: Dockerfile parse error line 52: nombre no válido para la etapa de compilación: "site_builder _ $ {host_env}", el nombre no puede comenzar con un número o contener símbolos
paulecoyote
9

Trabajar alrededor de la solución

Tenía el requisito de copiar FOLDER al servidor basado en ENV Variables. Tomé la imagen del servidor vacía. creó la estructura de la carpeta de implementación requerida en la carpeta local. luego se agrega debajo de la línea a DockerFile, copie la carpeta al contenedor. En la última línea se agregó un punto de entrada para ejecutar init file.sh antes de que Docker inicie el servidor.

#below lines added to integrate testing framework
RUN mkdir /mnt/conf_folder
ADD install /mnt/conf_folder/install
ADD install_test /mnt/conf_folder/install_test
ADD custom-init.sh /usr/local/bin/custom-init.sh
ENTRYPOINT ["/usr/local/bin/custom-init.sh"]

Luego cree el archivo custom-init.sh en local con un script como el siguiente

#!/bin/bash
if [ "${BUILD_EVN}" = "TEST" ]; then
    cp -avr /mnt/conf_folder/install_test/* /mnt/wso2das-3.1.0/
else
    cp -avr /mnt/conf_folder/install/* /mnt/wso2das-3.1.0/
fi;

En el archivo docker-compose debajo de las líneas.

entorno: - BUILD_EVN = TEST

Estos cambios copian la carpeta al contenedor durante la compilación de Docker. cuando ejecutamos docker-compose , copie o implemente la carpeta requerida real en el servidor antes de que se inicie.

Santhosh Hirekerur
fuente
8
Pero las imágenes de la ventana acoplable tienen capas. ADD copiaría estos a la imagen independientemente de la declaración if que mencionó ...
MyUserInStackOverflow
@MyUserInStackOverflow - Creo que la idea de esta "solución alternativa" es que tanto install como install_test se copian en la imagen, pero cuando se ejecuta la imagen, solo una de esas carpetas se copia en la ubicación final. Si está bien que ambos estén en algún lugar de la imagen, esta podría ser una técnica razonable.
ToolmakerSteve
4

Copie todos los archivos en un directorio desechable, elija manualmente el que desee y descarte el resto.

COPY . /throwaway
RUN cp /throwaway/requirements.txt . || echo 'requirements.txt does not exist'
RUN rm -rf /throwaway

Puede lograr algo similar usando etapas de compilación, que se basan en la misma solución, usando cppara copiar condicionalmente. Al usar una etapa de construcción, su imagen final no incluirá todo el contenido de la inicial COPY.

FROM alpine as copy_stage
COPY . .
RUN mkdir /dir_for_maybe_requirements_file
RUN cp requirements.txt /dir_for_maybe_requirements_file &>- || true

FROM alpine
# Must copy a file which exists, so copy a directory with maybe one file
COPY --from=copy_stage /dir_for_maybe_requirements_file /
RUN cp /dir_for_maybe_requirements_file/* . &>- || true
CMD sh
cdosborn
fuente
Si bien esto resuelve técnicamente el problema, no disminuye el tamaño de la imagen. Si está intentando copiar condicionalmente algo enorme (como un modelo de red profunda), aún aumenta el tamaño de la imagen, debido a la forma en que funciona la superposición fs.
DeusXMachina
@DeusXMachina, ¿qué versión de Docker estás usando? Los documentos contradicen lo que está diciendo docs.docker.com/develop/develop-images/multistage-build/… . Las capas no deben conservarse de una etapa de construcción no final.
cdosborn
@cdosburn - He observado esto el 18.09.2019. Hablaba principalmente sobre el primer ejemplo, las compilaciones por etapas deberían evitar ese problema. Y creo que cada etapa FROM se compacta ahora, pero me tienes adivinando mis recuerdos. Tendré que experimentar con algunas cosas.
DeusXMachina
@DeusXMachina, solo la segunda solución reduce el tamaño de la imagen.
cdosborn
esa es una buena solución para mi caso. ¡Copio cachey, dependiendo de cuál sea el caché, elijo qué hacer en los archivos de script!
Paschalis
1

Probé las otras ideas, pero ninguna cumplió con nuestro requisito. La idea es crear una imagen nginx base para aplicaciones web estáticas secundarias. Por razones de seguridad, optimización y estandarización, la imagen base debe poder RUNejecutar comandos en directorios agregados por imágenes secundarias. La imagen base no controla qué directorios agregan las imágenes secundarias. La suposición es que las imágenes de los niños serán COPYrecursos en algún lugar debajo COMMON_DEST_ROOT.

Este enfoque es un truco, pero la idea es que la imagen base admitirá la COPYinstrucción para 1 a N directorios agregados por la imagen secundaria. ARG PLACEHOLDER_FILEy ENV UNPROVIDED_DESTse utilizan para satisfacer <src>y <dest>requisitos para cualquier COPYno necesitan instrucción.

#
# base-image:01
#
FROM nginx:1.17.3-alpine
ENV UNPROVIDED_DEST=/unprovided
ENV COMMON_DEST_ROOT=/usr/share/nginx/html
ONBUILD ARG PLACEHOLDER_FILE
ONBUILD ARG SRC_1
ONBUILD ARG DEST_1
ONBUILD ARG SRC_2
ONBUILD ARG DEST_2
ONBUILD ENV SRC_1=${SRC_1:-PLACEHOLDER_FILE}
ONBUILD ENV DEST_1=${DEST_1:-${UNPROVIDED_DEST}}
ONBUILD ENV SRC_2=${SRC_2:-PLACEHOLDER_FILE}
ONBUILD ENV DEST_2=${DEST_2:-${UNPROVIDED_DEST}}

ONBUILD COPY ${SRC_1} ${DEST_1}
ONBUILD COPY ${SRC_2} ${DEST_2}

ONBUILD RUN sh -x \
    #
    # perform operations on COMMON_DEST_ROOT
    #
    && chown -R limited:limited ${COMMON_DEST_ROOT} \
    #
    # remove the unprovided dest
    #
    && rm -rf ${UNPROVIDED_DEST}

#
# child image
#
ARG PLACEHOLDER_FILE=dummy_placeholder.txt
ARG SRC_1=app/html
ARG DEST_1=/usr/share/nginx/html/myapp
FROM base-image:01

Esta solución tiene deficiencias obvias como el número ficticio PLACEHOLDER_FILEy codificado de instrucciones COPY que son compatibles. Además, no hay forma de deshacerse de las variables ENV que se utilizan en la instrucción COPY.

BrianNotBob
fuente