COPY / ADD condicional no Dockerfile?

103

Dentro dos meus Dockerfiles, gostaria de COPIAR um arquivo na minha imagem, se ele existir. O arquivo requirements.txt do pip parece um bom candidato, mas como isso seria feito?

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

ou

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
fonte
Por favor, veja aqui: docs.docker.com/reference/builder
Tuan
4
@Tuan - O que especificamente nesse link ajuda a fazer isso?
ToolmakerSteve

Respostas:

24

Isso não é suportado atualmente (como eu suspeito que levaria a uma imagem não reproduzível, já que o mesmo Dockerfile copiaria ou não o arquivo, dependendo de sua existência).

Isso ainda é solicitado, na edição 13045 , usando curingas: " COPY foo/* bar/" not work if no file in foo" (maio de 2015).
Não será implementado por agora (julho de 2015) no Docker, mas outra ferramenta de compilação como o bocker pode suportar isso.

VonC
fonte
32
boa resposta, mas a lógica docker, IMO, é falha. se você executar o mesmo dockerfile com um contexto de construção diferente, obterá uma imagem diferente. isso é esperado. usar o mesmo contexto de construção fornecerá a mesma imagem. e se você inserir instruções condicionais COPY / ADD no mesmo contexto de construção, obterá a mesma imagem. de modo que verifique. isso é apenas meus 2 centavos.
nathan g
Docker trata de infraestrutura imutável. Seus ambientes dev, staging e prod devem ser 99,99% o mais próximo possível, se não idênticos. Use variáveis ​​de ambiente.
AndrewMcLagan
3
@AndrewMcLagan e se, por exemplo, um devambiente front-end for executado com um servidor de desenvolvimento webpack e o prodambiente equivalente funcionar com uma /distpasta estática? Este é o caso na maioria das configurações de front-end hoje e, obviamente, deve prodnão pode ser o mesmo aqui. Então, como lidar com isso?
Jivan
Eu não uso o docker para desenvolver meus front ends de nó. O webpack normal localhost: 3000 etc ... Embora ainda inicialize seu ambiente docker dev local para que seu node / react / angular front end se comunique com o que estiver rodando em seu ambiente docker container normal. Por exemplo, APIs, redis, MySQL, mongo, pesquisa elástica e qualquer outro micro serviço. Você ..poderia .. rodar um ambiente de desenvolvimento webpack em um contêiner. Mas eu sinto que é muito longe ...
AndrewMcLagan
@Jivan Que tal usar uma imagem onbuild para definir as instruções comuns e então construir imagens específicas para dev e prod. O repositório Docker Hub Node parece conter imagens onbuild para cada versão do Node: hub.docker.com/_/node . Ou talvez você possa fazer o seu próprio.
david_i_smith
83

Aqui está uma solução alternativa simples:

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

Certifique-se de que fooexiste, pois COPYprecisa de pelo menos uma fonte válida.

Se file-which-may-existestiver presente, também será copiado.

NOTA: Você deve tomar cuidado para garantir que o seu caractere curinga não selecione outros arquivos que você não pretende copiar. Para ser mais cuidadoso, você pode usar file-which-may-exist?( ?corresponde a apenas um único caractere).

Ou melhor ainda, use uma classe de caractere como esta para garantir que apenas um arquivo possa ser correspondido:

COPY foo file-which-may-exis[t] /target
jdhildeb
fonte
1
Você pode fazer a mesma coisa com uma pasta?
Benjamin Toueg
1
@BenjaminToueg: Sim, de acordo com os documentos, você pode copiar arquivos e pastas.
jdhildeb
2
Isso funciona muito bem. Para arquivos com vários destinos, copiei para um diretório temporário e depois os movi para onde necessário. 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(onde a biblioteca compartilhada é a entidade desconhecida.)
Adam K Dean,
Ao copiar vários arquivos existentes, o destino deve ser um diretório. Como isso funciona quando foo e seu arquivo-que-pode-existir * existem?
melchoir55
1
Portanto, a resposta é 'certifique-se de que haja um arquivo' e, em seguida, uma demonstração de como usar o operador COPY? Não consigo ver como isso se relaciona com a pergunta original.
derrend
27

Conforme declarado por este comentário , a resposta de Santhosh Hirekerur ainda copia o arquivo, para arquivar uma cópia condicional verdadeira, você pode 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

As ONBUILDinstruções garantem que o arquivo só seja copiado se o "ramo" for selecionado pelo BUILD_ENV. Defina esta var usando um pequeno script antes de chamardocker build

Siyu
fonte
2
Eu gosto desta resposta porque abriu meus olhos não apenas para ONBUILD, que é muito útil, mas também parece mais fácil de integrar com outras variáveis ​​passadas, por exemplo, se você deseja definir a tag com base em BUILD_ENV, ou armazenar algum estado em ENV.
DeusXMachina
Acabei de tentar algo assim e obtive: Resposta de erro do daemon: Dockerfile parse error line 52: nome inválido para o estágio de compilação: "site_builder _ $ {host_env}", o nome não pode começar com um número ou conter símbolos
paulecoyote
9

Solução alternativa

Tive a necessidade de copiar a FOLDER para o servidor com base nas variáveis ​​ENV. Peguei a imagem do servidor vazia. criou a estrutura de pasta de implantação necessária na pasta local. em seguida, adicionado abaixo da linha para DockerFile copiar a pasta para o contêiner. Eu n última linha ponto de entrada adicional para executar file.sh inicialização antes janela de encaixe iniciar o 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"]

Em seguida, crie o arquivo custom-init.sh no local com um script semelhante a abaixo

#!/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;

No arquivo docker-compose abaixo das linhas.

ambiente: - BUILD_EVN = TEST

Essas alterações copiam a pasta para o contêiner durante a construção do docker. quando executamos docker-compose up , copie ou implante a pasta real necessária para o servidor antes de iniciar o servidor.

Santhosh Hirekerur
fonte
8
Mas as imagens do docker são em camadas. ADD iria copiar estes para a imagem, independentemente da instrução if que você mencionou ...
MyUserInStackOverflow
@MyUserInStackOverflow - Acho que a ideia dessa "solução alternativa" é que tanto install quanto install_test sejam copiados para a imagem, mas quando a imagem é executada, apenas uma dessas pastas é copiada para o local final. Se não houver problema em que ambos estejam em algum lugar da imagem, essa pode ser uma técnica razoável.
ToolmakerSteve
4

Copie todos os arquivos para um diretório descartável, escolha manualmente o que deseja e descarte o resto.

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

Você pode conseguir algo semelhante usando estágios de construção, que contam com a mesma solução, usando cppara copiar condicionalmente. Usando um estágio de construção, sua imagem final não incluirá todo o conteúdo da 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
fonte
Embora isso tecnicamente resolva o problema, não diminui o tamanho da imagem. Se você está tentando copiar condicionalmente algo grande (como um modelo de rede profunda), você ainda aumenta o tamanho da imagem, devido à maneira como a sobreposição fs funciona.
DeusXMachina
@DeusXMachina, qual versão do docker você está usando? Os documentos contradizem o que você está dizendo docs.docker.com/develop/develop-images/multistage-build/… . As camadas não devem ser preservadas de um estágio de construção não final.
cdosborn
@cdosburn - Eu observei isso em 18.09. Eu estava falando principalmente sobre o primeiro exemplo, compilações em etapas devem evitar esse problema. E eu acho que cada estágio FROM se compacta agora, mas você me faz adivinhar minhas lembranças. Terei que experimentar algumas coisas.
DeusXMachina
@DeusXMachina, certo apenas a segunda solução reduz o tamanho da imagem.
cdosborn
essa é uma boa solução para o meu caso. Eu copio um cachee dependendo do cache eu escolho o que fazer nos arquivos de script!
Paschalis,
1

Tentei as outras ideias, mas nenhuma atendeu aos nossos requisitos. A ideia é criar uma imagem nginx básica para aplicativos da web estáticos filhos. Por motivos de segurança, otimização e padronização, a imagem base deve ser capaz de RUNcomandos em diretórios adicionados por imagens filhas. A imagem base não controla quais diretórios são adicionados pelas imagens filhas. A suposição é que as imagens filhas terão COPYrecursos em algum lugar abaixo COMMON_DEST_ROOT.

Essa abordagem é um hack, mas a ideia é que a imagem base oferecerá suporte a COPYinstruções para diretórios 1 a N adicionados pela imagem filha. ARG PLACEHOLDER_FILEe ENV UNPROVIDED_DESTsão usados para satisfazer <src>e <dest>requisitos para qualquer COPYinstrução não necessário.

#
# 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 solução tem deficiências óbvias, como o número fictício PLACEHOLDER_FILEe codificado de instruções COPY que são suportadas. Além disso, não há como se livrar das variáveis ​​ENV que são usadas na instrução COPY.

brianNotBob
fonte