L'utilisation de l'instruction RUN dans un Dockerfile avec «source» ne fonctionne pas

274

J'ai un Dockerfile que je mets en place pour installer un environnement python vanille (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 s'exécute 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 suis lsdans 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'essaye d'exécuter la sourcecommande, j'obtiens la même erreur «introuvable» que ci-dessus. Cependant, si je lance 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 avec bonheur workon, mkvirtualenvetc.

J'ai fait quelques recherches, et au début, il semblait que le problème pourrait résider dans la différence entre bash en tant que shell de connexion Ubuntu et dash en tant que shell système Ubuntu , dash ne prenant pas en charge la sourcecommande.

Cependant, la réponse à cela semble être d'utiliser "." au lieu de source, mais cela provoque juste l'explosion du 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 d'exécuter l'image de base par défaut pour Ubuntu 12.04 LTS).

Hugo Rodger-Brown
la source
2
Alors ne le «sourcez» pas, exécutez simplement la commande. Ou exécutez spécifiquement le script shell avec 'bash'.
Alister Bulman
J'ai essayé - bien que le script n'échoue pas, je n'ai pas accès aux différentes commandes, ce que je voulais. Ce problème est la même chose - github.com/dotcloud/docker/issues/2847
Hugo Rodger-Brown
2
Ce qui, en y réfléchissant, est correct. Virtualenvwrapper n'a probablement aucun sens dans un environnement de conteneur. Je vais le retirer et utiliser à la place virtualenv «natif».
Hugo Rodger-Brown
1
La manière la plus fondamentale d'aborder cela est stackoverflow.com/questions/4732200/…
Gaurav Ojha
EssayezCMD source activate django-py35
Belter

Réponses:

316

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

chobo
la source
67
Eh? Si vous sourcez un script dans un shell qui n'existe que pour la commande, il n'est pas en mesure d'avoir un effet durable sur les futures commandes exécutées, en supposant que la somme totale de son action définit des variables d'environnement. Alors pourquoi utiliseriez-vous sourcedu tout, vs juste bash /usr/local/bin/virtualenvwrapper.sh, dans ce cas?
Charles Duffy
13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
Leo
29
Ce n'est pas correct même si cela fonctionne. Lisez docs.docker.com/engine/reference/builder/#run et ne vous arrêtez pas après le deuxième exemple de code. Lisez la note: qui suit immédiatement. Parce que /bin/sh -cc'est le shell par défaut, cette "forme shell" de RUN se traduit par RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]. Vous devriez aller de l'avant et utiliser la "forme exécutable" de RUN afin de pouvoir shsortir comme çaRUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
Bruno Bronosky
8
Veuillez consulter stackoverflow.com/a/45087082/117471 pour comprendre pourquoi cela crée un bashimbriqué dans un shet doit donc être évité.
Bruno Bronosky
4
Une bien meilleure réponse est ici: stackoverflow.com/a/42216046/1663462
Chris Stryczynski
150

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 Dockerfile que j'écris.

Modifier par un spectateur concerné

Si vous souhaitez obtenir l'effet «utiliser bashau lieu de shtout au long de ce Dockerfile» sans altérer et éventuellement endommager * le système d'exploitation à l'intérieur du conteneur, vous pouvez simplement dire à Docker votre intention . Cela se fait comme suit:

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

* Le dommage possible est que de nombreux scripts sous Linux (sur une nouvelle installation Ubuntu grep -rHInE '/bin/sh' /renvoie plus de 2700 résultats) attendent un shell entièrement POSIX à /bin/sh. Le shell bash n'est pas seulement POSIX plus des extensions supplémentaires. Il existe des fonctions intégrées (et plus) qui se comportent entièrement différemment de celles de POSIX. Je supporte ENTIÈREMENT d'éviter POSIX (et l'erreur que tout script que vous n'avez pas testé sur un autre shell va fonctionner parce que vous pensez avoir évité les basmismes) et d'utiliser simplement bashism. Mais vous faites cela avec un shebang approprié dans votre script. Pas en tirant le shell POSIX hors de l'ensemble du système d'exploitation. (Sauf si vous avez le temps de vérifier tous les scripts 2700 plus fournis avec Linux ainsi que tous ceux des packages que vous installez.)

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

Anubhav Sinha
la source
18
Cela peut être un peu simplifié:ln -snf /bin/bash /bin/sh
apottere
2
@ user1442219 cela remplace l'interpréteur de commandes par défaut de shàbash
Bhargav Nanekalva
27
ln -s /bin/bash /bin/shc'est une terrible idée. ubuntu cible / bin / sh pour se lancer pour une raison. dash est un shell entièrement posix qui est plus rapide que bash. lier / bin / sh à bash réduira considérablement les performances de votre serveur. citer: wiki.ubuntu.com/DashAsBinSh
xero
7
C'est un hack sale, pas une solution. Si votre script est exécuté par le shshell, mais que vous le souhaitez bash, la solution appropriée consiste à shinvoquer le processus de bashmanière ponctuelle, par exemple bash -c 'source /script.sh && …', ou vous pouvez même aller jusqu'à éviter complètement les bashismes (comme source), et à la place choisissez de n'utiliser que des équivalents POSIX valides, par exemple . /script.sh. (Attention à l'espace après le .!) Enfin, si votre script est exécutable (pas seulement sourceable), ne faites jamais mentir votre script avec un #!/bin/shshebang s'il n'est pas réellement compatible sh. Utilisez #!/bin/bashplutôt.
Mark G.
7
Et maintenant, comment puis-je downvote la réponse originale et upvote la modification par «un concerné»?
Slava
65

Le shell par défaut de l' RUNinstruction est ["/bin/sh", "-c"].

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

En utilisant l' instruction SHELL , vous pouvez modifier le shell par défaut pour les RUNinstructions 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 supplémentaire : vous pouvez également ajouter une --loginoption permettant de démarrer un shell de connexion. Cela signifie que, ~/.bachrcpar exemple, serait lu et que vous n'avez pas besoin de le source explicitement avant votre commande

Ahmad Abdelghany
la source
1
Excellent pointeur sur l'utilisation --login- je viens de comprendre cela moi
mattexx
1
En utilisant, SHELL ["/bin/bash", "-c", "-l"] j'ai pu utiliser d'autres mises à jour du fichier .bashrc, ce qui m'a permis d'exécuter facilement les commandes asdf.
Rowinson Gallego
46

J'ai eu le même problème et pour 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.

Andrea Grandi
la source
Dans le cas où vous venez de réponses ROS, oui cela fonctionne. Quelque chose comme:RUN /bin/bash -c "source /opt/ros/melodic/setup.bash && \ cd /home && \ git clone https://angelos.p:[email protected]/inno/grpc-comms.git && \ cd grpc-comms && \ mkdir build && \ cd build && \ cmake .. && make"
angelos.p
44

La manière la plus simple est d'utiliser l'opérateur point à la place de la source, qui est l'équivalent sh de la sourcecommande bash :

Au lieu de:

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

Utilisation:

RUN . /usr/local/bin/virtualenvwrapper.sh
mixja
la source
"la source est un bourne shell intégré et un POSIX" spécial "intégré - ss64.com/bash/source.html linux.die.net/man/1/sh ... . / sourceaccepte également les paramètres de position après le nom de fichier
Wes Turner
5
Cela ne fonctionne pas car chaque commande RUN fonctionne indépendamment. Les modifications de sourceou .sont perdues à la fin de la commande RUN. Voir: stackoverflow.com/a/40045930/19501
2018
26

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:

depuis 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 shell des commandes. Le shell par défaut sous Linux est ["/ bin / sh", "-c"] et sous Windows est ["cmd", "/ S", "/ C"]. L'instruction SHELL doit être écrite sous forme JSON dans un Dockerfile.

L'instruction SHELL est particulièrement utile sous Windows où il existe deux shells natifs couramment utilisés et très 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 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 shell est utilisée dans un Dockerfile: RUN, CMD et ENTRYPOINT.

L'exemple suivant est un modèle courant trouvé sur Windows qui peut être rationalisé à l'aide de l'instruction SHELL:

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

La commande invoqué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 (aka shell) non nécessaire est appelé. Deuxièmement, chaque instruction RUN sous forme de 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é. La première 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 soit sans ambiguïté et n'utilise pas le cmd.exe inutile, il nécessite plus de verbosité par des guillemets doubles et des échappements. L'autre mécanisme consiste à utiliser l'instruction SHELL et la forme shell, créant une syntaxe plus naturelle pour les utilisateurs de Windows, en particulier lorsqu'elle est combinée avec 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, en utilisant SHELL cmd / S / C / V: ON | OFF sous Windows, la sémantique d'expansion des variables d'environnement différé peut être modifiée.

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

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

Mithril
la source
20

En m'appuyant sur les réponses de cette page, j'ajouterais que vous devez être conscient que chaque instruction RUN s'exécute indépendamment des autres /bin/sh -cet qu'elle n'obtiendra donc aucune variable d'environnement qui proviendrait normalement des shells de connexion.

La meilleure façon que j'ai trouvée jusqu'à présent est d'ajouter le script à /etc/bash.bashrc, puis d'appeler chaque commande en tant que connexion 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 l'environnement virtuel, l'activer lorsque vous utilisez une connexion bash, puis installer vos modules python dans cet environnement:

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 permet de comprendre ce qui provient quand.

TomDotTom
la source
1
Cool, basé sur votre solution, ce que j'ai fait juste pour illustrer était: ADD env-file /etc/profile.d/installerenv.sh RUN /bin/bash --login -c 'env' RUN /bin/bash -c 'rm /etc/profile.d/installerenv.sh' si son cas d'utilisation ajoute plus de variables d'environnement d'injection à la perspective de construction de docker comme la mienne, je recommanderais de consulter docs.docker.com/compose/yml / # fichier-env aussi.
daniel.kahlenberg
1
Le problème avec cela, je crois, est que vous ne finissez pas par mettre en cache les résultats de chaque RUNcommande, ce qui signifie que vous ne pouvez pas installer beaucoup de dépendances de projet, puis copier sur le code source et profiter des avantages de Mise en cache des étapes intermédiaires de Docker. Il réinstallera toutes les dépendances du projet à chaque fois.
erewok
Utiliser /etc/bashrcpour Redhat, au lieu de /etc/bash.bashrccomme mentionné ci-dessus (pour Ubuntu)
Jordan Gee
J'ai utilisé /root/.bashrc pour les centos.
schmudu
RUN echo "source /yourscript.bash" >> /etc/bash.bashrc fait l'affaire. si vous utilisez ros inside docker et que vous souhaitez configurer l'environnement
voici
17

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

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

Sinon, l'utilisation de la «forme shell» de RUN et la spécification d'un shell différent aboutissent à 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 plus d'une commande qui nécessite un shell différent, vous devriez lire https://docs.docker.com/engine/reference/builder/#shell et changer votre shell par défaut en plaçant ceci avant vos commandes RUN:

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

Enfin, si vous avez placé quelque chose dans le .bashrcfichier de l'utilisateur root dont vous avez besoin, vous pouvez ajouter l' -lindicateur à la commande SHELLou RUNpour en faire un shell de connexion et vous assurer qu'il est fourni.

Remarque: j'ai intentionnellement ignoré le fait qu'il est inutile de source un script comme la seule commande dans un RUN.

Bruno Bronosky
la source
1
SHELL ["/bin/sh", "-c", "-l"]donc il source ~ / .bashrc etc dans le cas où vous avez des paramètres d'environnement depuis le conteneur de base
MortenB
1
@MortenB, mais vous avez spécifié (typé?) Ce /bin/shqui ne résoudra pas le problème de bash non utilisé. De plus, lors de la réalisation d'un, docker buildil est peu probable qu'il y ait quelque chose d'utile dans le .bashrc de l'utilisateur root dont vous avez besoin. Mais, si vous y mettez quelque chose plus tôt dans le Dockerfile (comme peut-être un JAVA_HOME, alors oui. Je mettrai une note à ce sujet dans ma réponse.
Bruno Bronosky
Désolé pour la faute de frappe, j'utilise pyenv qui a besoin de source ~ / .bashrc pour définir les chemins pour la version correcte de python dans mes images de base. cela me fait utiliser n'importe quelle base linux et avec deux lignes ajouter n'importe quelle version sur python. Comme python 3.7 sur ubuntu16.04 où le python de base est 3.5.2
MortenB
11

Selon la documentation Docker

Pour utiliser un shell différent, 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

Gianluca Casati
la source
C'est la bonne réponse RÉELLE. L'auteur de la réponse sélectionnée stackoverflow.com/a/25086628/117471 ne semble avoir lu que le premier exemple de la documentation à laquelle vous accédez. Ils ne semblent pas avoir lu le paragraphe suivant qui est ce que vous avez cité.
Bruno Bronosky
4

Si vous en avez, SHELLvous devriez utiliser cette réponse - n'utilisez pas celle qui est acceptée, ce qui vous oblige à mettre le reste du dockerfile dans une seule commande par ce commentaire .

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

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

Notez que -iest nécessaire pour que bash lise le fichier rc.

Mohan
la source
3

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

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

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

vimdude
la source
3

J'ai également eu des problèmes lors de l'exécution sourcedans un Dockerfile

Cela fonctionne parfaitement pour la construction du conteneur Docker de CentOS 6.6, mais a posé 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
vikas027
la source
2

Cela peut se produire car il sources'agit d'une fonction intégrée à bash plutôt que d'un binaire quelque part sur le système de fichiers. Votre intention est-elle que le script que vous souriez modifie le conteneur par la suite?

Paul Morie
la source
1
Le script met à jour le conteneur - mais pour être honnête, j'essayais de faire quelque chose qui n'avait aucun sens, alors j'ai ignoré le problème.
Hugo Rodger-Brown
1

J'ai fini par mettre mes trucs env et j'ai .profilemuté SHELLquelque 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
mattexx
la source
3
"-c" doit être le dernier argument (avant l'exécution de la commande)
peterk
0

Si vous essayez simplement d'utiliser pip pour installer quelque chose dans virtualenv, vous pouvez modifier le PATH env pour qu'il regarde d'abord dans le dossier bin de virtualenv

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

Ensuite, toutes les pip installcommandes qui suivent dans le Dockerfile trouveront d'abord / path / to / venv / bin / pip et l'utiliseront, qui s'installera dans ce virtualenv et non dans le python système.

shadfc
la source