COPY / ADD conditionnel dans Dockerfile?

103

À l'intérieur de mes Dockerfiles, je voudrais COPIER un fichier dans mon image s'il existe, le fichier requirements.txt pour pip semble être un bon candidat, mais comment cela serait-il réalisé?

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
la source
S'il vous plaît voir ici: docs.docker.com/reference/builder
Tuan
4
@Tuan - Qu'est-ce qui aide précisément à ce lien?
ToolmakerSteve

Réponses:

24

Ce n'est actuellement pas pris en charge (car je soupçonne que cela conduirait à une image non reproductible, car le même Dockerfile copierait ou non le fichier, en fonction de son existence).

Ceci est toujours demandé, dans le numéro 13045 , en utilisant des caractères génériques: " COPY foo/* bar/" not work if no file in foo" (mai 2015).
Il ne sera pas implémenté pour l'instant (juillet 2015) dans Docker, mais un autre outil de construction comme bocker pourrait le supporter.

VonC
la source
33
bonne réponse, mais la logique docker, IMO, est imparfaite. si vous exécutez le même dockerfile avec un contexte de construction différent, vous obtiendrez une image différente. c'est à prévoir. utiliser le même contexte de construction donnera la même image. et si vous insérez des instructions COPY / ADD conditionnelles sur le même contexte de construction, vous obtiendrez la même image. de sorte que vérifie. c'est juste mes 2 cents.
nathan g
Docker concerne une infrastructure immuable. Vos environnements dev, staging et prod doivent être à 99,99% aussi proches que possible sinon identiques. Utilisez des variables d'environnement.
AndrewMcLagan
3
@AndrewMcLagan que se passe-t-il si, par exemple, un devenvironnement frontal s'exécute avec un serveur de développement Webpack et que l' prodenvironnement équivalent fonctionne avec un /distdossier statique? C'est le cas dans la plupart des configurations frontales aujourd'hui, et évidemment devet prodne peut pas être la même chose ici. Alors, comment gérer ça?
Jivan
Je n'utilise pas docker pour développer les frontaux de mes nœuds. Le webpack localhost normal: 3000 etc. Par exemple, API, redis, MySQL, mongo, recherche élastique et tout autre micro service. Vous pourriez ... exécuter un environnement de développement webpack dans un conteneur. Mais je pense que c'est trop loin ...
AndrewMcLagan
@Jivan Que diriez-vous d'utiliser une image onbuild pour définir les instructions communes, puis de créer des images spécifiques pour le développement et la production. Le référentiel Docker Hub Node semble contenir des images onbuild pour chaque version de Node: hub.docker.com/_/node . Ou peut-être que vous pourriez rouler votre propre
david_i_smith
84

Voici une solution de contournement simple:

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

Assurez-vous qu'il fooexiste, car COPYnécessite au moins une source valide.

Si file-which-may-existest présent, il sera également copié.

REMARQUE: vous devez veiller à ce que votre caractère générique ne récupère pas d'autres fichiers que vous n'avez pas l'intention de copier. Pour être plus prudent, vous pouvez utiliser à la file-which-may-exist?place ( ?correspond à un seul caractère).

Ou mieux encore, utilisez une classe de caractères comme celle-ci pour vous assurer qu'un seul fichier peut être mis en correspondance:

COPY foo file-which-may-exis[t] /target
jdhildeb
la source
1
Pouvez-vous faire la même chose avec un dossier?
Benjamin Toueg
1
@BenjaminToueg: Oui, selon la documentation, vous pouvez copier des fichiers ainsi que des dossiers.
jdhildeb
2
Cela fonctionne très bien. Pour les fichiers avec plusieurs destinations, j'ai copié dans un répertoire temporaire, puis je les ai déplacés là où c'était nécessaire. 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(où la bibliothèque partagée est l'entité inconnue.)
Adam K Dean
Lors de la copie de plusieurs fichiers existants, la destination doit être un répertoire. Comment cela fonctionne-t-il lorsque foo et votre fichier-qui-peut-exister * existent?
melchoir55
1
Donc, la réponse est «assurez-vous qu'il y a un fichier», puis une démonstration sur la façon d'utiliser l'opérateur COPY? Je ne vois pas en quoi cela se rapporte à la question initiale.
derrend le
27

Comme indiqué par ce commentaire , la réponse de Santhosh Hirekerur copie toujours le fichier, pour archiver une véritable copie conditionnelle, vous pouvez utiliser cette méthode.

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

Les ONBUILDinstructions garantissent que le fichier n'est copié que si la "branche" est sélectionnée par le BUILD_ENV. Définissez cette variable à l'aide d'un petit script avant d'appelerdocker build

Siyu
la source
2
J'aime cette réponse car elle m'a ouvert les yeux non seulement sur ONBUILD, ce qui est super pratique, mais elle semble également la plus facile à intégrer avec d'autres variables transmises, par exemple si vous souhaitez définir la balise basée sur BUILD_ENV, ou stocker un état dans ENV.
DeusXMachina
J'ai juste essayé quelque chose comme ça et j'ai obtenu: Réponse d'erreur du démon: ligne d'erreur d'analyse Dockerfile 52: nom invalide pour l'étape de construction: "site_builder _ $ {host_env}", le nom ne peut pas commencer par un nombre ou contenir des symboles
paulecoyote
9

Solution de contournement

J'avais besoin de copier le dossier sur le serveur en fonction des variables ENV. J'ai pris l'image du serveur vide. a créé la structure de dossier de déploiement requise dans le dossier local. puis ajouté ci-dessous la ligne à DockerFile copiez le dossier dans le conteneur. I n dernière ligne a ajouté un point d'entrée pour exécuter init file.sh avant que docker ne démarre le serveur.

#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"]

Ensuite, créez le fichier custom-init.sh en local avec un script comme ci-dessous

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

Dans le fichier docker-compose sous les lignes.

environnement: - BUILD_EVN = TEST

Ces modifications copient le dossier dans le conteneur lors de la génération du docker. lorsque nous exécutons docker-compose, copiez ou déployez le dossier requis sur le serveur avant le démarrage du serveur.

Santhosh Hirekerur
la source
8
Mais les images de docker sont superposées. ADD les copierait dans l'image indépendamment de l'instruction if que vous avez mentionnée ...
MyUserInStackOverflow
@MyUserInStackOverflow - Je pense que l'idée de cette "solution de contournement" est que install et install_test sont copiés dans l'image, mais lorsque l'image est exécutée, un seul de ces dossiers est copié à l'emplacement final. S'il est normal que les deux soient quelque part dans l'image, cela pourrait être une technique raisonnable.
ToolmakerSteve
5

Copiez tous les fichiers dans un répertoire jetable, choisissez celui que vous voulez, supprimez le reste.

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

Vous pouvez obtenir quelque chose de similaire en utilisant les étapes de construction, qui repose sur la même solution, en utilisant cppour copier certaines conditions. En utilisant une étape de construction, votre image finale n'inclura pas tout le contenu de l'initiale 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
la source
Bien que cela résout techniquement le problème, cela ne diminue pas la taille de l'image. Si vous essayez de copier conditionnellement quelque chose d'énorme (comme un modèle de réseau profond), vous gonflez toujours la taille de l'image, en raison du fonctionnement de la superposition fs.
DeusXMachina
@DeusXMachina, quelle version de docker utilisez-vous? Les documents contredisent ce que vous dites docs.docker.com/develop/develop-images/multistage-build/… . Les couches ne doivent pas être préservées d'une étape de construction non finale.
cdosborn
@cdosburn - J'ai observé cela le 18.09. Je parlais principalement du premier exemple, les builds par étapes devraient éviter ce problème. Et je pense que chaque étape de FROM se compacte maintenant, mais vous me faites remettre en question mes souvenirs. Je vais devoir expérimenter certaines choses.
DeusXMachina
@DeusXMachina, à droite seule la deuxième solution réduit la taille de l'image.
cdosborn
c'est une belle solution de contournement pour mon cas. Je copie un cacheet en fonction de ce que c'est le cache je choisis quoi faire dans les fichiers de script!
Paschalis
1

J'ai essayé les autres idées, mais aucune ne répondait à nos exigences. L'idée est de créer une image nginx de base pour les applications Web statiques enfants. Pour des raisons de sécurité, d'optimisation et de standardisation, l'image de base doit pouvoir RUNexécuter des commandes sur des répertoires ajoutés par des images enfants. L'image de base ne contrôle pas les répertoires ajoutés par les images enfants. L'hypothèse est que les images enfants seront des COPYressources quelque part sous COMMON_DEST_ROOT.

Cette approche est un hack, mais l'idée est que l'image de base prendra en charge les COPYinstructions pour 1 à N répertoires ajoutés par l'image enfant. ARG PLACEHOLDER_FILEet ENV UNPROVIDED_DESTsont utilisés pour satisfaire <src>et <dest>exigences pour une COPYinstruction non nécessaires.

#
# 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

Cette solution présente des lacunes évidentes telles que le nombre factice PLACEHOLDER_FILEet codé en dur des instructions COPY prises en charge. De plus, il n'y a aucun moyen de se débarrasser des variables ENV qui sont utilisées dans l'instruction COPY.

brianNotBob
la source