Comment inclure des fichiers en dehors du contexte de construction de Docker?


462

Comment puis-je inclure des fichiers en dehors du contexte de construction de Docker en utilisant la commande "ADD" dans le fichier Docker?

Dans la documentation Docker:

Le chemin doit être à l'intérieur du contexte de la construction; vous ne pouvez pas ADD ../something/something, car la première étape d'une génération de docker consiste à envoyer le répertoire de contexte (et ses sous-répertoires) au démon de docker.

Je ne veux pas restructurer tout mon projet juste pour accueillir Docker dans cette affaire. Je souhaite conserver tous mes fichiers Docker dans le même sous-répertoire.

En outre, il semble que Docker ne prend pas encore (et peut-être jamais) en charge les liens symboliques: la commande Dockerfile ADD ne suit pas les liens symboliques sur l'hôte # 1676.

La seule autre chose à laquelle je peux penser est d'inclure une étape de pré-génération pour copier les fichiers dans le contexte de génération Docker (et configurer mon contrôle de version pour ignorer ces fichiers). Existe-t-il une meilleure solution de contournement que cela?


94
Cela doit être la pire chose à propos de Docker. De mon point de vue, il n'y a pas de "projet Docker". Docker est destiné aux projets d'expédition. C'est juste un outil. Je ne veux pas avoir à reconstruire tout mon projet pour héberger docker, en ajoutant .dockerignore etc. À la fin de la journée, qui sait combien de temps Docker durera? Ce serait bien d'avoir une séparation entre le code (c'est-à-dire le projet angulaire) et tous les moyens pour le déployer (c'est-à-dire docker). Après tout, il n'y a vraiment aucun avantage à avoir un fichier docker à côté de tout le reste. Il suffit de câbler les choses pour créer une image :(
TigerBear

3
Ouais, c'est un gros inconvénient. Je suis confronté au même problème et j'ai un fichier binaire de plus grande taille (déjà compressé) que je ne veux pas copier dans chaque contexte de construction Docker. Je préfère le source depuis son emplacement actuel (en dehors du contexte de construction Docker). Et je ne veux pas mapper un volume au moment de l'exécution, car j'essaie de COPIER / AJOUTER le fichier au moment de la construction et de décompresser et de faire ce dont j'ai besoin pour que certains binaires soient intégrés dans l'image. De cette façon, la rotation des conteneurs est rapide.
jersey bean

J'ai trouvé une bonne structure et j'explique avec des détails sur stackoverflow.com/a/53298446/433814
Marcello de Sales

1
le problème avec les builds docker est le concept inventé de "contexte". Les Dockerfiles ne sont pas suffisants pour définir une construction, à moins qu'ils ne soient placés sous un répertoire stratégique (aka contexte), c'est-à-dire "/" comme un extrême, donc vous pouvez accéder à n'importe quel chemin (notez que ce n'est pas la bonne chose à faire dans un projet sain soit ..., en plus cela rend les builds de docker très lents car docker scanne tout le contexte au démarrage). Vous pouvez envisager de créer une image de docker avec tous les fichiers requis et FROMde continuer à partir de là. Je ne changerais pas la structure du projet pour accueillir Docker (ou tout autre outil de construction).
Devis L.

Réponses:


414

La meilleure façon de contourner ce problème est de spécifier le Dockerfile indépendamment du contexte de génération, en utilisant -f.

Par exemple, cette commande donnera à la commande ADD accès à tout ce qui se trouve dans votre répertoire actuel.

docker build -f docker-files/Dockerfile .

Mise à jour : Docker permet désormais d'avoir le Dockerfile en dehors du contexte de construction (corrigé dans 18.03.0-ce, https://github.com/docker/cli/pull/886 ). Vous pouvez donc aussi faire quelque chose comme

docker build -f ../Dockerfile .

8
@Ro. vous utilisez la dockerfile:propriété dans la build:section du fichier Compose docs.docker.com/compose/compose-file/#/compose-file-reference
Emerson Farrugia

3
J'obtiens "Le Dockerfile doit être dans le contexte de construction" - J'aimerais vraiment avoir un Dockerfile qui peut résider en dessous du contexte de construction actuel. Dans votre exemple, vous avez le Dockerfile dans / sous le contexte de construction actuel, qui bien sûr fonctionne.
Alexander Mills

3
Oui, je veux juste un Dockerfile partagé qui correspond à plusieurs sous-répertoires, qui sont tous des "contextes de construction"
Alexander Mills

51
Est-ce que cela résout le problème de l'OP de vouloir ADDun fichier qui est en dehors du répertoire de contexte? C'est ce que j'essaie de faire, mais je ne pense pas que l'utilisation -fde fichiers externes puisse être ajoutée.
Sridhar Sarnobat

18
Vous n'upvote jamais assez .. dans mon docker-compose.yml je: build: context: .., dockerfile: dir/Dockerfile. Maintenant, mon contexte de construction est le répertoire parent!
Mike Gleason jr Couturier

51

Je me retrouve souvent à utiliser l' --build-argoption à cette fin. Par exemple, après avoir mis ce qui suit dans le Dockerfile:

ARG SSH_KEY
RUN echo "$SSH_KEY" > /root/.ssh/id_rsa

Vous pouvez simplement faire:

docker build -t some-app --build-arg SSH_KEY="$(cat ~/file/outside/build/context/id_rsa)" .

Mais notez l'avertissement suivant de la documentation Docker :

Avertissement: Il n'est pas recommandé d'utiliser des variables au moment de la construction pour transmettre des secrets comme les clés github, les informations d'identification de l'utilisateur, etc. Les valeurs des variables au moment de la construction sont visibles par tout utilisateur de l'image avec la commande docker history.


6
Ce sont de mauvais conseils sans un énorme avertissement. À partir de la documentation Docker: "Avertissement: il n'est pas recommandé d'utiliser des variables au moment de la construction pour transmettre des secrets comme les clés github, les informations d'identification de l'utilisateur, etc. Les valeurs des variables au moment de la construction sont visibles par tout utilisateur de l'image avec la commande historique du docker." [1] En d'autres termes, l'exemple donné dans cet exemple divulgue la clé SSH privée dans l'image de docker. Dans certains contextes, cela pourrait convenir. docs.docker.com/engine/reference/builder/#arg
sheldonh

3
Enfin, pour surmonter ce problème de sécurité, vous pouvez utiliser des techniques telles que le squash ou les builds en plusieurs étapes: vsupalov.com/build-docker-image-clone-private-repo-ssh-key
Jojo

46

Sous Linux, vous pouvez monter d'autres répertoires au lieu de les créer des liens symboliques

mount --bind olddir newdir

Voir /superuser/842642 pour plus de détails.

Je ne sais pas si quelque chose de similaire est disponible pour d'autres systèmes d'exploitation. J'ai également essayé d'utiliser Samba pour partager un dossier et le remonter dans le contexte Docker qui fonctionnait également.


2
Seul root peut lier des répertoires
jjcf89

28

J'ai passé un bon moment à essayer de trouver un bon modèle et à mieux expliquer ce qui se passe avec cette prise en charge des fonctionnalités. J'ai réalisé que la meilleure façon de l'expliquer était la suivante ...

  • Dockerfile: ne verra que les fichiers sous son propre chemin relatif
  • Contexte: un endroit dans "l'espace" où les fichiers que vous souhaitez partager et votre Dockerfile seront copiés

Donc, cela dit, voici un exemple du Dockerfile qui doit réutiliser un fichier appelé start.sh

Dockerfile

Il ALWAYSse chargera à partir de son chemin d'accès relatif, ayant le répertoire courant de lui-même comme localréférence aux chemins que vous spécifiez.

COPY start.sh /runtime/start.sh

Des dossiers

Compte tenu de cette idée, nous pouvons penser à avoir plusieurs copies pour les Dockerfiles construisant des choses spécifiques, mais ils ont tous besoin d'accéder à start.sh.

./all-services/
   /start.sh
   /service-X/Dockerfile
   /service-Y/Dockerfile
   /service-Z/Dockerfile
./docker-compose.yaml

Compte tenu de cette structure et des fichiers ci-dessus, voici un docker-compose.yml

docker-compose.yaml

  • Dans cet exemple, votre sharedrépertoire de contexte est leruntime répertoire.
    • Même modèle mental ici, pensez que tous les fichiers sous ce répertoire sont déplacés vers le soi-disant context .
    • De même, spécifiez simplement le Dockerfile que vous souhaitez copier dans ce même répertoire. Vous pouvez spécifier cela en utilisant dockerfile.
  • le répertoire où se trouve votre contenu principal est le contexte réel à définir.

Le docker-compose.ymlest comme suit

version: "3.3"
services:

  service-A
    build:
      context: ./all-service
      dockerfile: ./service-A/Dockerfile

  service-B
    build:
      context: ./all-service
      dockerfile: ./service-B/Dockerfile

  service-C
    build:
      context: ./all-service
      dockerfile: ./service-C/Dockerfile
  • all-serviceest défini comme contexte, le fichier partagé y start.shest copié ainsi que le Dockerfile spécifié par chaquedockerfile .
  • Chacun peut être construit à sa façon, en partageant le fichier de démarrage!

À votre santé!


1
Votre point Dockerfile n'est pas vrai completly, comme l' a par la réponse acceptée, si vous êtes dans une hiérarchie de dossiers a/b/c, alors oui en cours d' exécution docker build .en cne vous permettra pas d'accéder ../file-in-b. Mais, je pense que le malentendu général dans ce (ou du moins le mien l'était) est que le contexte est défini par l'emplacement indiqué par le premier argument de la commande build, et non par l'emplacement du Dockerfile. Donc, comme indiqué dans la réponse acceptée: de a: docker build -f a/b/c/Dockerfile . signifie que dans le Dockerfile .est maintenant le dossiera
β.εηοιτ.βε

1
Citant à partir des documents Dockerfile: les chemins des fichiers et des répertoires seront interprétés comme relatifs à la source du contexte de la construction.
Nishant George Agrwal

18

Si vous lisez la discussion du numéro 2745, non seulement Docker peut ne jamais prendre en charge les liens symboliques, mais également ne jamais prendre en charge l'ajout de fichiers en dehors de votre contexte. Semble être une philosophie de conception selon laquelle les fichiers qui entrent dans la construction de docker devraient explicitement faire partie de son contexte ou provenir d'une URL où il est vraisemblablement déployé aussi avec une version fixe afin que la construction soit reproductible avec des URL bien connues ou des fichiers livrés avec le conteneur de docker.

Je préfère construire à partir d'une source contrôlée par version - ie docker build -t stuff http://my.git.org/repo - sinon je construis à partir d'un endroit aléatoire avec des fichiers aléatoires.

fondamentalement, non ... - SvenDowideit, Docker Inc

Juste mon avis mais je pense que vous devriez restructurer pour séparer le code et les référentiels docker. De cette façon, les conteneurs peuvent être génériques et extraire n'importe quelle version du code au moment de l'exécution plutôt qu'au moment de la génération.

Sinon, utilisez docker comme artefact de déploiement de code fondamental, puis placez le fichier docker à la racine du référentiel de code. si vous suivez cette route, il est probablement judicieux d'avoir un conteneur Docker parent pour des détails plus généraux sur le niveau du système et un conteneur enfant pour une configuration spécifique à votre code.


Pourquoi alors utiliser Docker?
lscoughlin

11

Je pense que la solution de contournement la plus simple serait de changer le «contexte» lui-même.

Ainsi, par exemple, au lieu de donner:

docker build -t hello-demo-app .

qui définit le répertoire courant comme contexte, disons que vous vouliez le répertoire parent comme contexte, utilisez simplement:

docker build -t hello-demo-app ..

6
Je pense que cela casse .dockerignore: - \
NullVoxPopuli

J'ai abandonné .dockerignore et j'ai plutôt créé un dossier de docker géré Makefile qui ne contient que les fichiers nécessaires au contexte de génération ... Je n'ai besoin que d'appeler make buildet il extrait tous les fichiers nécessaires s'ils ont été mis à jour, puis il appelle la construction de docker appropriée ... J'ai besoin de faire un travail supplémentaire, mais cela fonctionne parfaitement parce que je suis en plein contrôle.
Sahsahae


3

En utilisant docker-compose, j'ai accompli cela en créant un service qui monte les volumes dont j'ai besoin et en validant l'image du conteneur. Ensuite, dans le service suivant, je me fie à l'image précédemment validée, qui contient toutes les données stockées aux emplacements montés. Vous devrez ensuite copier ces fichiers vers leur destination finale, car les répertoires montés sur l'hôte ne sont pas validés lors de l'exécution d'une docker commitcommande

Vous n'avez pas besoin d'utiliser docker-compose pour y parvenir, mais cela vous facilite un peu la vie

# docker-compose.yml

version: '3'
  services:
    stage:
      image: alpine
      volumes:
        - /host/machine/path:/tmp/container/path
      command: bash -c "cp -r /tmp/container/path /final/container/path"
    setup:
      image: stage
# setup.sh

# Start "stage" service
docker-compose up stage

# Commit changes to an image named "stage"
docker commit $(docker-compose ps -q stage) stage

# Start setup service off of stage image
docker-compose up setup

1

J'ai eu ce même problème avec un projet et certains fichiers de données que je n'ai pas pu déplacer dans le contexte du référentiel pour des raisons HIPPA. J'ai fini par utiliser 2 Dockerfiles. On construit l'application principale sans les éléments dont j'avais besoin en dehors du conteneur et les publie sur le référentiel interne. Ensuite, un deuxième dockerfile extrait cette image et ajoute les données et crée une nouvelle image qui est ensuite déployée et jamais stockée nulle part. Pas idéal, mais cela a fonctionné à mes fins de garder les informations sensibles hors du référentiel.


1

Une solution de contournement simple pourrait être de simplement monter le volume (en utilisant l'indicateur -v ou --mount) sur le conteneur lorsque vous l'exécutez et d'accéder aux fichiers de cette façon.

exemple:

docker run -v /path/to/file/on/host:/desired/path/to/file/in/container/ image_name

pour plus d'informations, voir: https://docs.docker.com/storage/volumes/


Notez que cela ne fonctionne que si le volume est une dépendance d'exécution. Pour les dépendances de temps de construction, il docker runest trop tard.
user3735633

1

Comme cela est décrit dans ce problème GitHub, la génération se produit réellement /tmp/docker-12345, donc un chemin relatif comme ../relative-add/some-fileest relatif à /tmp/docker-12345. Il rechercherait ainsi/tmp/relative-add/some-file , ce qui est également indiqué dans le message d'erreur. *

Il n'est pas autorisé d'inclure des fichiers en dehors du répertoire de génération, ce qui entraîne le message "Chemin d'accès interdit". "


0

Une façon simple et rapide consiste à définir le contexte de génération sur autant de niveaux que nécessaire, mais cela peut avoir des conséquences. Si vous travaillez dans une architecture de microservices qui ressemble à ceci:

./Code/Repo1
./Code/Repo2
...

Vous pouvez définir le contexte de génération sur le parent Code répertoire , puis accéder à tout, mais il s'avère qu'avec un grand nombre de référentiels, cela peut entraîner une longue durée de génération.

Un exemple de situation pourrait être qu'une autre équipe gère un schéma de base de données dans Repo1et le code de votre équipe dansRepo2 dépend. Vous voulez ancrer cette dépendance avec certaines de vos propres données de départ sans vous soucier des changements de schéma ou polluer le référentiel de l'autre équipe (selon les changements, vous devrez peut-être toujours changer vos scripts de données de départ bien sûr) La deuxième approche est hacky mais contourne la question des versions longues:

Créez un script sh (ou ps1) ./Code/Repo2pour copier les fichiers dont vous avez besoin et appelez les commandes docker de votre choix, par exemple:

#!/bin/bash
rm -r ./db/schema
mkdir ./db/schema

cp  -r ../Repo1/db/schema ./db/schema

docker-compose -f docker-compose.yml down
docker container prune -f
docker-compose -f docker-compose.yml up --build

Dans le fichier docker-compose, définissez simplement le contexte en tant que Repo2root et utilisez le contenu du ./db/schemarépertoire dans votre dockerfile sans vous soucier du chemin. Gardez à l'esprit que vous courrez le risque de commettre accidentellement ce répertoire dans le contrôle de code source, mais les actions de nettoyage de script devraient être assez faciles.


0

Dans mon cas, mon Dockerfile est écrit comme un modèle contenant des espaces réservés que je remplace par une valeur réelle en utilisant mon fichier de configuration.

Je ne pouvais donc pas spécifier ce fichier directement mais le canaliser dans la construction du docker comme ceci:

sed "s/%email_address%/$EMAIL_ADDRESS/;" ./Dockerfile | docker build -t katzda/bookings:latest . -f -;

Mais à cause du tuyau, la COPYcommande n'a pas fonctionné. Mais la manière ci-dessus le résout par -f -(en disant explicitement que le fichier n'est pas fourni). Ne faire que -sans l' -findicateur, le contexte ET le Dockerfile ne sont pas fournis, ce qui est une mise en garde.


0

L'astuce consiste à reconnaître que vous pouvez spécifier le contexte dans la commande de génération pour inclure les fichiers du répertoire parent si vous spécifiez le chemin Docker, je changerais mon Dockerfile pour qu'il ressemble à ceci:

...
COPY ./ /dest/
...

Ensuite, ma commande de construction peut ressembler à ceci:

docker built -t TAG -f DOCKER_FILE_PATH CONTEXT

Depuis le répertoire du projet

docker built -t username/project[:tag] -f ./docker/Dockerfile .

Du projet / docker

docker built -t username/project[:tag] -f ./docker/Dockerfile ..
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.