L’utilisation de l’instruction RUN dans un fichier Docker avec ‘source’ ne fonctionne pas

J’ai un fichier Dockerfile que je rassemble pour installer un environnement Python vanille (dans lequel je vais installer une application, mais à une date ultérieure).

FROM ubuntu:12.04 # required to build certain python libraries RUN apt-get install python-dev -y # install pip - canonical installation instructions from pip-installer.org # http://www.pip-installer.org/en/latest/installing.html ADD https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py /tmp/ez_setup.py ADD https://raw.github.com/pypa/pip/master/consortingb/get-pip.py /tmp/get-pip.py RUN python /tmp/ez_setup.py RUN python /tmp/get-pip.py RUN pip install --upgrade pip # install and configure virtualenv RUN pip install virtualenv RUN pip install virtualenvwrapper ENV WORKON_HOME ~/.virtualenvs RUN mkdir -p $WORKON_HOME RUN source /usr/local/bin/virtualenvwrapper.sh 

La compilation fonctionne correctement jusqu’à la dernière ligne, où je reçois l’exception suivante:

 [previous steps 1-9 removed for clarity] ... Successfully installed virtualenvwrapper virtualenv-clone stevedore Cleaning up... ---> 1fc253a8f860 Step 10 : ENV WORKON_HOME ~/.virtualenvs ---> Running in 8b0145d2c80d ---> 0f91a5d96013 Step 11 : RUN mkdir -p $WORKON_HOME ---> Running in 9d2552712ddf ---> 3a87364c7b45 Step 12 : RUN source /usr/local/bin/virtualenvwrapper.sh ---> Running in c13a187261ec /bin/sh: 1: source: not found 

Si je suis dans ce répertoire (juste pour tester que les étapes précédentes ont été validées), je peux voir que les fichiers existent comme prévu:

 $ docker run 3a87 ls /usr/local/bin easy_install easy_install-2.7 pip pip-2.7 virtualenv virtualenv-2.7 virtualenv-clone virtualenvwrapper.sh virtualenvwrapper_lazy.sh 

Si j’essaie juste d’exécuter la commande source j’obtiens la même erreur “non trouvé” que ci-dessus. Si je cours une session shell interactive, la source fonctionne:

 $ docker run 3a87 bash source bash: line 1: source: filename argument required source: usage: source filename [arguments] 

Je peux exécuter le script à partir d’ici, puis accéder joyeusement à workon , mkvirtualenv etc.

J’ai fait quelques recherches, et au départ, il semblait que le problème pouvait provenir de la différence entre le shell de connexion Ubuntu comme bash et celui du shell du système Ubuntu, le dash ne prenant pas en charge la commande source .

Cependant, la réponse à cela semble être d’utiliser “.” au lieu de source , mais cela provoque simplement l’exécution de Docker avec une exception de panique.

Quelle est la meilleure façon d’exécuter un script shell à partir d’une instruction Dockerfile RUN pour contourner ce problème (je suis en train de tourner l’image de base par défaut pour Ubuntu 12.04 LTS).

RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh"

Réponse originale

 FROM ubuntu:14.04 RUN rm /bin/sh && ln -s /bin/bash /bin/sh 

Cela devrait fonctionner pour chaque image de base de docker Ubuntu. J’ajoute généralement cette ligne pour chaque fichier Dockerfile que j’écris.

Modifier par un témoin concerné

Si vous voulez obtenir l’effet de “utiliser bash au lieu de sh dans tout le fichier Dockerfile”, sans altérer et éventuellement endommager le système d’exploitation à l’intérieur du conteneur, vous pouvez simplement indiquer votre intention à Docker . Cela se fait comme ça:

 SHELL ["/bin/bash", "-c"] 

* Le dommage possible est que de nombreux scripts sous Linux (sur une nouvelle installation Ubuntu grep -rHInE '/bin/sh' / retourne plus de 2700 résultats) attendent un shell POSIX complet à /bin/sh . Le shell bash n’est pas seulement POSIX plus des fonctions supplémentaires. Il existe des intégrales (et plus) qui se comportent entièrement différemment de celles de POSIX. J’accepte complètement d’éviter POSIX (et l’erreur selon laquelle tout script que vous n’avez pas testé sur un autre shell va fonctionner parce que vous pensez avoir évité les bases) et simplement utiliser le bashisme. Mais vous le faites avec un vrai shebang dans votre script. Pas en retirant le shell POSIX de tout le système d’exploitation. (Sauf si vous avez le temps de vérifier tous les scripts 2700 plus fournis avec Linux, plus tous ceux inclus dans les packages que vous installez.)

Plus de détails dans cette réponse ci-dessous. https://stackoverflow.com/a/45087082/117471

J’ai eu le même problème et pour exécuter pip installation dans virtualenv j’ai dû utiliser cette commande:

 RUN pip install virtualenv virtualenvwrapper RUN mkdir -p /opt/virtualenvs ENV WORKON_HOME /opt/virtualenvs RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh \ && mkvirtualenv myapp \ && workon myapp \ && pip install -r /mycode/myapp/requirements.txt" 

J’espère que ça aide.

Le moyen le plus simple est d’utiliser l’opérateur point à la place de la source, qui est l’équivalent de la commande source bash:

Au lieu de:

 RUN source /usr/local/bin/virtualenvwrapper.sh 

Utilisation:

 RUN . /usr/local/bin/virtualenvwrapper.sh 

Vérifiez la commande SHELL . Le shell par défaut sous Linux est [“/ bin / sh”, “-c”]

 RUN "source file" # translates to: RUN /bin/sh -c "source file" 

Vous pouvez modifier le shell par défaut en utilisant SHELL qui modifie le shell utilisé pour les instructions RUN suivantes dans Dockerfile

 SHELL ["/bin/bash", "-c"] 

Maintenant, le shell par défaut a changé et vous n’avez pas besoin de le définir explicitement dans chaque instruction RUN

 RUN "source file" # now translates to: RUN /bin/bash -c "source file" 

Note supplémentaire : Vous pouvez également append l’option --login qui --login un shell de connexion. Cela signifie que ~/.bachrc par exemple serait lu et que vous n’avez pas besoin de le rechercher explicitement avant votre commande

En me basant sur les réponses données sur cette page, j’appendai que chaque instruction RUN s’exécute indépendamment des autres avec /bin/sh -c et n’obtient donc pas de vars d’environnement normalement issus des shells de connexion.

Le meilleur moyen que j’ai trouvé jusqu’à présent est d’append le script à /etc/bash.bashrc , puis d’appeler chaque commande en tant que login bash.

 RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc RUN /bin/bash --login -c "your command" 

Vous pouvez par exemple installer et configurer virtualenvwrapper, créer le env virtuel, l’activer lorsque vous utilisez un login bash, puis installer vos modules python dans cet env:

 RUN pip install virtualenv virtualenvwrapper RUN mkdir -p /opt/virtualenvs ENV WORKON_HOME /opt/virtualenvs RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc RUN /bin/bash --login -c "mkvirtualenv myapp" RUN echo "workon mpyapp" >> /etc/bash.bashrc RUN /bin/bash --login -c "pip install ..." 

La lecture du manuel sur les fichiers de démarrage bash aide à comprendre ce qui provient de quand.

Si vous utilisez Docker 1.12 ou plus récent, utilisez simplement SHELL !

Réponse courte:

général:

 SHELL ["/bin/bash", "-c"] 

pour python vituralenv:

 SHELL ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"] 

Longue réponse:

à partir de https://docs.docker.com/engine/reference/builder/#/shell

 SHELL ["executable", "parameters"] 

L’instruction SHELL permet de remplacer le shell par défaut utilisé pour les commandes de type shell. Le shell par défaut sous Linux est [“/ bin / sh”, “-c”] et sous Windows, “[cmd”, “/ S”, “/ C”]. L’instruction SHELL doit être écrite sous forme JSON dans un fichier Docker.

L’instruction SHELL est particulièrement utile sous Windows où il y a deux shells natifs couramment utilisés et très différents: cmd et powershell, ainsi que des shells alternatifs disponibles, y compris sh.

L’instruction SHELL peut apparaître plusieurs fois. Chaque instruction SHELL remplace toutes les instructions SHELL précédentes et affecte toutes les instructions suivantes. Par exemple:

 FROM microsoft/windowsservercore # Executed as cmd /S /C echo default RUN echo default # Executed as cmd /S /C powershell -command Write-Host default RUN powershell -command Write-Host default # Executed as powershell -command Write-Host hello SHELL ["powershell", "-command"] RUN Write-Host hello # Executed as cmd /S /C echo hello SHELL ["cmd", "/S"", "/C"] RUN echo hello 

Les instructions suivantes peuvent être affectées par l’instruction SHELL lorsque leur forme de shell est utilisée dans un fichier Dockerfile: RUN, CMD et ENTRYPOINT.

L’exemple suivant est un modèle commun trouvé sous Windows, qui peut être rationalisé à l’aide de l’instruction SHELL:

 ... RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" ... 

La commande appelée par docker sera:

 cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" 

Ceci est inefficace pour deux raisons. Tout d’abord, un processeur de commandes cmd.exe (aka shell) non nécessaire est appelé. Deuxièmement, chaque instruction RUN de la forme shell nécessite une commande powershell supplémentaire préfixant la commande.

Pour rendre cela plus efficace, l’un des deux mécanismes peut être utilisé. L’une consiste à utiliser la forme JSON de la commande RUN, telle que:

 ... RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""] ... 

Bien que le formulaire JSON ne soit pas ambigu et n’utilise pas le fichier cmd.exe inutile, il nécessite plus de verbosité en citant et en échappant. L’autre mécanisme consiste à utiliser l’instruction SHELL et la forme du shell, ce qui rend la syntaxe plus naturelle pour les utilisateurs Windows, en particulier lorsqu’elle est associée à la directive d’échappement parser:

 # escape=` FROM microsoft/nanoserver SHELL ["powershell","-command"] RUN New-Item -ItemType Directory C:\Example ADD Execute-MyCmdlet.ps1 c:\example\ RUN c:\example\Execute-MyCmdlet -sample 'hello world' 

Résultant en:

 PS E:\docker\build\shell> docker build -t shell . Sending build context to Docker daemon 4.096 kB Step 1/5 : FROM microsoft/nanoserver ---> 22738ff49c6d Step 2/5 : SHELL powershell -command ---> Running in 6fcdb6855ae2 ---> 6331462d4300 Removing intermediate container 6fcdb6855ae2 Step 3/5 : RUN New-Item -ItemType Directory C:\Example ---> Running in d0eef8386e97 Directory: C:\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 10/28/2016 11:26 AM Example ---> 3f2fbf1395d9 Removing intermediate container d0eef8386e97 Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\ ---> a955b2621c31 Removing intermediate container b825593d39fc Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world' ---> Running in be6d8e63fe75 hello world ---> 8e559e9bf424 Removing intermediate container be6d8e63fe75 Successfully built 8e559e9bf424 PS E:\docker\build\shell> 

L’instruction SHELL pourrait également être utilisée pour modifier la manière dont fonctionne un shell. Par exemple, en utilisant SHELL cmd / S / C / V: ON | OFF sous Windows, la sémantique d’extension de variable d’environnement retardée pourrait être modifiée.

L’instruction SHELL peut également être utilisée sous Linux si un autre shell est requirejs, tel que zsh, csh, tcsh et autres.

La fonctionnalité SHELL a été ajoutée dans Docker 1.12.

Selon la documentation de Docker

Pour utiliser un autre shell, autre que / bin / sh, utilisez la forme exec en passant dans le shell souhaité. Par exemple,

 RUN ["/bin/bash", "-c", "echo hello"] 

Voir https://docs.docker.com/engine/reference/builder/#run

Selon https://docs.docker.com/engine/reference/builder/#run, le shell par défaut [Linux] pour RUN est /bin/sh -c . Vous semblez vous attendre à des bashismes, vous devez donc utiliser la forme “exec” de RUN pour spécifier votre shell.

 RUN ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"] 

Sinon, l’utilisation de la “forme de shell” de RUN et la spécification d’un shell différent entraînent la création de shells nesteds.

 # don't do this... RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh" # because it is the same as this... RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"] 

Si vous avez plus d’une commande nécessitant un shell différent, vous devriez lire https://docs.docker.com/engine/reference/builder/#shell et changer votre shell par défaut en le plaçant avant vos commandes RUN:

 SHELL ["/bin/bash", "-c"] 

Enfin, si vous avez placé quelque chose dans le fichier .bashrc l’utilisateur racine, vous pouvez append l’ -l à la commande SHELL ou RUN pour en faire un shell de connexion et vous assurer qu’il est généré.

Remarque: J’ai intentionnellement ignoré le fait qu’il est inutile de générer un script en tant que seule commande dans une exécution.

J’ai aussi eu des problèmes avec l’exécution du source dans un fichier Dockerfile

Cela fonctionne parfaitement pour la construction du conteneur Docker CentOS 6.6, mais a donné des problèmes dans les conteneurs Debian

 RUN cd ansible && source ./hacking/env-setup 

C’est comme ça que je l’ai abordé, ce n’est peut-être pas une manière élégante, mais c’est ce qui a fonctionné pour moi

 RUN echo "source /ansible/hacking/env-setup" >> /tmp/setup RUN /bin/bash -C "/tmp/setup" RUN rm -f /tmp/setup 

Vous voudrez peut-être lancer bash -v pour voir ce qui a été généré.

Je ferais ce qui suit au lieu de jouer avec des liens symboliques:

RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc

Cela peut être dû au fait que source est un système intégré à bash plutôt qu’un fichier binary quelque part sur le système de fichiers. Votre intention pour le script que vous recherchez est-elle de modifier le conteneur par la suite?

Si vous essayez juste d’utiliser pip pour installer quelque chose dans virtualenv, vous pouvez modifier le envoi PATH pour qu’il apparaisse d’abord dans le dossier bin de virtualenv.

ENV PATH="/path/to/venv/bin:${PATH}"

Ensuite, toutes les commandes d’ pip install qui suivent dans le fichier Docker trouveront / path / to / venv / bin / pip en premier et les utiliseront, ce qui les installera dans virtualenv et non dans le système python.

J’ai fini par mettre mon truc env dans .profile et muté SHELL quelque chose comme

 SHELL ["/bin/bash", "-c", "-l"] # Install ruby version specified in .ruby-version RUN rvm install $(< .ruby-version) # Install deps RUN rvm use $(<.ruby-version) && gem install bundler && bundle install CMD rvm use $(<.ruby-version) && ./myscript.rb