web-dev-qa-db-fra.com

L'utilisation de l'instruction RUN dans un fichier Docker avec "source" ne fonctionne pas

Je prépare un fichier Docker pour installer un environnement Vanilla python (dans lequel j'installerai 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/contrib/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 construction fonctionne correctement jusqu'à la dernière ligne, où j'obtiens 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 ls dans ce répertoire (juste pour vérifier 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 simplement d'exécuter la commande source, j'obtiens la même erreur 'introuvable' que ci-dessus. Si j'exécute une session interactive Shell cependant, 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 avec bonheur à workon, mkvirtualenv etc.

J'ai creusé un peu, et au départ, le problème semblait résider dans la différence entre bash comme étant le shell de connexion Ubuntu , et tiret en tant que système Ubuntu Shell , tiret ne prenant pas en charge la source commande.

Cependant, la solution semble être d'utiliser '.' au lieu de source, mais cela provoque simplement le temps d'exécution de Docker avec une exception de panique.

Quel est le meilleur moyen d’exécuter un script Shell à partir d’une instruction Dockerfile RUN pour contourner ce problème (j’exécute l’image de base par défaut pour Ubuntu 12.04 LTS).

218
Hugo Rodger-Brown

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

274
chobo

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 du docker Ubuntu. J'ajoute généralement cette ligne pour chaque fichier Docker que j'écris.

Editer par un spectateur concerné

Si vous voulez obtenir l’effet de "utiliser bash au lieu de sh dans tout ce fichier Dockerfile", sans modifier Alt et endommageant éventuellement * le système d'exploitation à l'intérieur du conteneur, vous pouvez simplement indiquer votre intention à Docker . Cela se fait comme suit:

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

* Les dommages possibles sont que de nombreux scripts sous Linux (sur une nouvelle installation Ubuntu grep -rHInE '/bin/sh' / renvoie plus de 2700 résultats) attendent un shell POSIX complet à /bin/sh. Le shell bash n’est pas seulement POSIX plus des éléments supplémentaires. Il y a des fonctions intégrées (et plus) qui se comportent de manière totalement différente de celles de POSIX. Je suis tout à fait en faveur de l’évitement de POSIX (et de 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 basmismes) et n’utilisez que le bashisme. Mais vous faites cela avec un Shebang approprié dans votre script. Pas en tirant le shell POSIX de tout le système d'exploitation. (Sauf si vous avez le temps de vérifier tous les 2700 scripts fournis avec Linux, ainsi que ceux de tous les packages que vous installez.)

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

136
Anubhav Sinha

J'ai eu le même problème et afin d'exécuter l'installation de pip 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.

43
Andrea Grandi

Le shell par défaut pour l'instruction RUN est ["/bin/sh", "-c"].

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

En utilisant instruction Shell , vous pouvez modifier le shell par défaut 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"

Remarque complémentaire : Vous pouvez également ajouter l’option --login pour démarrer 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

41
Ahmad Abdelghany

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

Au lieu de:

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

Utilisation:

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

En me basant sur les réponses de cette page, j’ajouterais que vous devez être conscient du fait que chaque instruction RUN s’exécute indépendamment des autres avec /bin/sh -c et qu’elle n’obtiendra donc aucun var d’environnement qui proviendrait normalement de shell de connexion.

Le meilleur moyen que j’ai trouvé jusqu’à présent consiste à ajouter le script à /etc/bash.bashrc, puis à appeler chaque commande sous le nom bash login.

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 l’environnement 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 ..."

Lire le manuel sur fichiers de démarrage bash aide à comprendre ce qui est acheté quand.

20
TomDotTom

Si vous utilisez Docker 1.12 ou une version plus récente, 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 la forme de commande 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 existe deux shell natifs couramment utilisés et assez différents: cmd et powershell, ainsi que d'autres shells disponibles, y compris sh.

L'instruction Shell peut apparaître plusieurs fois. Chaque instruction Shell remplace toutes les instructions précédentes du shell 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 Shell est utilisée dans un fichier Docker: RUN, CMD et ENTRYPOINT.

L'exemple suivant est un modèle commun trouvé sur Windows qui peut être simplifié à 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 commande cmd.exe non nécessaire (appelé Shell) est appelé. Deuxièmement, chaque instruction RUN sous la forme Shell nécessite une commande powershell supplémentaire préfixant la commande.

Pour rendre cela plus efficace, un des deux mécanismes peut être utilisé. La première consiste à utiliser le formulaire JSON de la commande RUN, tel que:

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

Bien que le formulaire JSON soit sans ambiguïté et n'utilise pas le cmd.exe non nécessaire, il nécessite davantage de verbosité via des guillemets doubles et des échappements. Le mécanisme alternatif consiste à utiliser l'instruction Shell et la forme Shell, rendant ainsi la syntaxe plus naturelle pour les utilisateurs Windows, en particulier lorsqu'elle est combiné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 peut également être utilisée pour modifier le fonctionnement d'un shell. Par exemple, à l'aide de Shell cmd/S/C/V: ON | OFF sous Windows, la sémantique d'expansion des variables d'environnement différée peut être modifiée.

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

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

19
Mithril

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

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

Sinon, l'utilisation du "formulaire Shell" de RUN et la spécification d'un shell différent génèrent des shells imbriqués.

# 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 plusieurs commandes nécessitant un autre shell, vous devez lire https://docs.docker.com/engine/reference/builder/#Shell et modifier votre shell par défaut en le plaçant avant. vos commandes RUN:

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

Enfin, si vous avez placé quoi que ce soit dans le fichier .bashrc de l'utilisateur root dont vous avez besoin, vous pouvez ajouter l'indicateur -l à la commande Shell ou RUN pour en faire un login. Shell et assurez-vous qu'il est recherché.

Remarque: j'ai volontairement ignoré le fait qu'il est inutile de créer un script en tant que commande unique d'un RUN.

14
Bruno Bronosky

Selon la documentation de Docker

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

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

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

9
Gianluca Casati

J'ai également eu des problèmes pour exécuter source dans un fichier Docker

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

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

C’est ainsi que j’ai abordé le sujet, ce n’est peut-être pas une façon é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
3
vikas027

Cela peut être dû au fait que source est une commande intégrée à bash plutôt qu'un fichier binaire situé quelque part sur le système de fichiers. Votre intention est-elle que le script que vous sourcez modifie le conteneur par la suite?

2
Paul Morie

Vous voudrez peut-être exécuter bash -v pour voir ce qui est recherché.

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

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

2
vimdude

J'ai fini par mettre mes fichiers 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
1
mattexx

Si vous essayez simplement d’utiliser pip pour installer quelque chose dans virtualenv, vous pouvez modifier l’environnement PATH pour qu’il cherche d’abord dans le dossier bin de virtualenv.

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

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

0
shadfc

Si vous avez Shell disponible, vous devriez utiliser cette réponse - ne pas utiliser celui qui est accepté, ce qui vous oblige à mettre le reste du fichier docker dans une commande par ce commentaire .

Si vous utilisez une ancienne version de Docker et que vous n'avez pas accès à Shell, cela fonctionnera tant que vous n'avez besoin de rien de .bashrc (ce qui est un cas rare dans Dockerfiles):

ENTRYPOINT ["bash", "--rcfile", "/usr/local/bin/virtualenvwrapper.sh", "-ci"]

Notez que -i est nécessaire pour que bash lise le fichier rcfile.

0
Mohan