Usar la instrucción RUN en un Dockerfile con 'fuente' no funciona

274

Tengo un Dockerfile que estoy armando para instalar un entorno de python vainilla (en el que instalaré una aplicación, pero en una fecha posterior).

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 compilación funciona bien hasta la última línea, donde obtengo la siguiente excepción:

[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 entro lsen ese directorio (solo para probar que se confirmaron los pasos anteriores) puedo ver que los archivos existen como se esperaba:

$ 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 intento simplemente ejecutar el sourcecomando, obtengo el mismo error 'no encontrado' que el anterior. Sin embargo, si EJECUTO una sesión de shell interactiva, la fuente funciona:

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

Puedo ejecutar el script desde aquí, y luego acceder felizmente workon, mkvirtualenvetc.

He cavado un poco, e inicialmente parecía que el problema podría estar en la diferencia entre bash como el shell de inicio de sesión de Ubuntu y dash como el shell del sistema de Ubuntu , el dash no admite el sourcecomando.

Sin embargo, la respuesta a esto parece ser usar '.' en lugar de source, pero esto solo hace que el tiempo de ejecución de Docker explote con una excepción de pánico.

¿Cuál es la mejor manera de ejecutar un script de shell desde una instrucción Dockerfile RUN para evitar esto (estoy ejecutando la imagen base predeterminada para Ubuntu 12.04 LTS)?

Hugo Rodger-Brown
fuente
2
Así que no lo 'fuente', solo ejecute el comando. O ejecute específicamente el script de shell con 'bash'.
Alister Bulman
Intenté eso, aunque el script no falla, no tengo acceso a los diversos comandos, que era lo que quería. Este problema es el mismo: github.com/dotcloud/docker/issues/2847
Hugo Rodger-Brown
2
Lo cual, pensando en ello, es correcto. Virtualenvwrapper probablemente no tiene sentido en un entorno contenedor. Voy a retroceder y usar virtualenv 'nativo' en su lugar.
Hugo Rodger-Brown
1
La forma más fundamental de abordar esto es stackoverflow.com/questions/4732200/…
Gaurav Ojha
PruebaCMD source activate django-py35
Belter

Respuestas:

316

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

chobo
fuente
67
Eh? Si obtiene un script dentro de un shell que solo existe para el comando, no podrá tener un efecto duradero en ningún comando futuro ejecutado, suponiendo que la suma total de su acción establezca variables de entorno. Entonces, ¿por qué usarías, sourceen lugar de solo bash /usr/local/bin/virtualenvwrapper.sh, en ese caso?
Charles Duffy
13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
Leo
29
Esto no es correcto aunque funcione. Lea docs.docker.com/engine/reference/builder/#run y no se detenga después del segundo ejemplo de código. Lea la nota: que sigue inmediatamente. Debido a que /bin/sh -ces el shell predeterminado, esta "forma de shell" de RUN se traduce en RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]. Deberías seguir adelante y usar la "forma ejecutiva" de RUN para que puedas shsacarlo asíRUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
Bruno Bronosky
8
Consulte stackoverflow.com/a/45087082/117471 para comprender por qué esto crea un bashanidado en shy, por lo tanto, debe evitarse.
Bruno Bronosky
44
Una respuesta mucho mejor está aquí: stackoverflow.com/a/42216046/1663462
Chris Stryczynski el
150

Respuesta original

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

Esto debería funcionar para cada imagen base de Docker de Ubuntu. Generalmente agrego esta línea para cada Dockerfile que escribo.

Editar por un espectador preocupado

Si desea obtener el efecto de "usar en bashlugar de en shtodo este Dockerfile", sin alterar y posiblemente dañar * el sistema operativo dentro del contenedor, puede decirle a Docker su intención . Eso se hace así:

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

* El posible daño es que muchos scripts en Linux (en una nueva instalación de Ubuntu grep -rHInE '/bin/sh' /devuelve más de 2700 resultados) esperan un shell POSIX completo en /bin/sh. El bash shell no es solo POSIX más componentes adicionales. Hay incorporados (y más) que se comportan completamente diferentes a los de POSIX. APOYO TOTALMENTE evitar POSIX (y la falacia de que cualquier script que no haya probado en otro shell funcionará porque cree que evitó los basmismos) y simplemente usar el bashismo. Pero lo haces con un shebang adecuado en tu guión. No extrayendo el shell POSIX de todo el sistema operativo. (A menos que tenga tiempo para verificar todos los scripts 2700 plus que vienen con Linux más todos los paquetes que instale).

Más detalles en esta respuesta a continuación. https://stackoverflow.com/a/45087082/117471

Anubhav Sinha
fuente
18
Esto se puede simplificar un poco:ln -snf /bin/bash /bin/sh
Apottere
2
@ user1442219 esto reemplaza el intérprete de comandos predeterminado de shabash
Bhargav Nanekalva
27
ln -s /bin/bash /bin/shEsta es una idea terrible. Ubuntu apunta / bin / sh al guión por una razón. dash es un shell completamente posix que es de órdenes de magnitud más rápido que bash. vincular / bin / sh a bash reducirá drásticamente el rendimiento de su servidor. cita: wiki.ubuntu.com/DashAsBinSh
xero
77
Este es un truco sucio, no una solución. Si el script está siendo ejecutado por el shshell, pero lo desea bash, la solución adecuada es hacer que el shproceso se invoque de bashmanera única, por ejemplo bash -c 'source /script.sh && …', o incluso podría llegar al extremo de evitar bashismos (como source) por completo, y en su lugar optar por usar solo equivalentes POSIX válidos, por ejemplo . /script.sh. (¡Cuidado con el espacio después del .!) Por último, si su script es ejecutable (no solo fuente), nunca haga que su script descanse con un #!/bin/shshebang si no es realmente compatible con sh. Usar en su #!/bin/bashlugar.
Mark G.
77
Y ahora, ¿cómo puedo rechazar la respuesta original y votar la edición por 'un interesado'?
Slava
65

El shell predeterminado para la RUNinstrucción es ["/bin/sh", "-c"].

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

Con la instrucción SHELL , puede cambiar el shell predeterminado para las RUNinstrucciones posteriores en Dockerfile:

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

Ahora, el shell predeterminado ha cambiado y no necesita definirlo explícitamente en cada instrucción RUN

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

Nota adicional : También puede agregar la --loginopción que iniciaría un shell de inicio de sesión. Esto significa que, ~/.bachrcpor ejemplo, se leería y no necesita obtenerlo explícitamente antes de su comando

Ahmad Abdelghany
fuente
1
Gran indicador sobre el uso --login- solo lo descubrí yo mismo
mattexx
1
Al usar SHELL ["/bin/bash", "-c", "-l"] pude usar más actualizaciones para el archivo .bashrc, lo que me permitió ejecutar comandos asdf fácilmente.
Rowinson Gallego
46

Tuve el mismo problema y para ejecutar pip install dentro de virtualenv tuve que usar este comando:

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"

Espero que ayude.

Andrea Grandi
fuente
En caso de que vinieras de las respuestas de ROS, sí, eso funciona. Algo así como: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 manera más simple es usar el operador de punto en lugar de la fuente, que es el equivalente sh del sourcecomando bash :

En vez de:

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

Utilizar:

RUN . /usr/local/bin/virtualenvwrapper.sh
mixja
fuente
"la fuente es un bourne shell incorporado y un POSIX 'especial' incorporado" - ss64.com/bash/source.html linux.die.net/man/1/sh ... . / sourcetambién acepta parámetros posicionales después del nombre del archivo
Wes Turner
55
Esto no funciona ya que cada comando RUN funciona de forma independiente. Los cambios de sourceo .se pierden cuando finaliza el comando EJECUTAR. Ver: stackoverflow.com/a/40045930/19501
amit el
26

Si está utilizando Docker 1.12 o más reciente, ¡simplemente utilícelo SHELL!

Respuesta corta:

general:

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

para python vituralenv:

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

Respuesta larga:

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

SHELL ["executable", "parameters"]

La instrucción SHELL permite anular el shell predeterminado utilizado para la forma de shell de los comandos. El shell predeterminado en Linux es ["/ bin / sh", "-c"], y en Windows es ["cmd", "/ S", "/ C"]. La instrucción SHELL debe escribirse en forma JSON en un Dockerfile.

La instrucción SHELL es particularmente útil en Windows donde hay dos shells nativos de uso común y bastante diferentes: cmd y powershell, así como shells alternativos disponibles, incluido sh.

La instrucción SHELL puede aparecer varias veces. Cada instrucción SHELL anula todas las instrucciones SHELL anteriores y afecta a todas las instrucciones posteriores. Por ejemplo:

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

Las siguientes instrucciones pueden verse afectadas por la instrucción SHELL cuando su forma de shell se utiliza en un Dockerfile: RUN, CMD y ENTRYPOINT.

El siguiente ejemplo es un patrón común que se encuentra en Windows que se puede simplificar mediante el uso de la instrucción SHELL:

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

El comando invocado por docker será:

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

Esto es ineficiente por dos razones. Primero, se invoca un procesador de comandos cmd.exe innecesario (también conocido como shell). En segundo lugar, cada instrucción RUN en la forma de shell requiere un comando extra de PowerShell que prefija el comando.

Para hacer esto más eficiente, se puede emplear uno de dos mecanismos. Una es usar la forma JSON del comando EJECUTAR como:

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

Si bien el formulario JSON es inequívoco y no utiliza el cmd.exe innecesario, sí requiere más verbosidad mediante comillas dobles y escape. El mecanismo alternativo es utilizar la instrucción SHELL y la forma de shell, creando una sintaxis más natural para los usuarios de Windows, especialmente cuando se combina con la directiva del analizador de escape:

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

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

La instrucción SHELL también podría usarse para modificar la forma en que opera un shell. Por ejemplo, usando SHELL cmd / S / C / V: ON | OFF en Windows, la semántica de expansión variable de entorno retrasada podría modificarse.

La instrucción SHELL también se puede usar en Linux si se requiere un shell alternativo como zsh, csh, tcsh y otros.

La función SHELL se agregó en Docker 1.12.

Mitril
fuente
20

Sobre la base de las respuestas en esta página, agregaría que debe tener en cuenta que cada instrucción RUN se ejecuta independientemente de las demás /bin/sh -cy, por lo tanto, no obtendrá ningún valor de entorno que normalmente se obtenga en shells de inicio de sesión.

La mejor manera que he encontrado hasta ahora es agregar el script /etc/bash.bashrcy luego invocar cada comando como inicio de sesión bash.

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

Por ejemplo, puede instalar y configurar virtualenvwrapper, crear el entorno virtual, activarlo cuando use un inicio de sesión bash y luego instalar sus módulos de Python en este entorno:

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 ..."

Leer el manual sobre los archivos de inicio de bash ayuda a comprender de qué se origina cuándo.

TomDotTom
fuente
1
Genial, basado en su solución, lo que hice para ilustrar fue: 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 el caso de uso de uno agrega más variables de entorno de inyección a la perspectiva de construcción de Docker como la mía, recomendaría echar un vistazo a docs.docker.com/compose/yml / # env-file también.
daniel.kahlenberg
1
El problema con esto, creo, es que no terminas almacenando en caché los resultados de cada RUNcomando, lo que significa que no puedes instalar una gran cantidad de dependencias del proyecto y luego copiar el código fuente y aprovechar los beneficios de Caché de paso intermedio de Docker. Reinstalará todas las dependencias del proyecto cada vez.
Erewok
Uso /etc/bashrcpara Redhat, en lugar de /etc/bash.bashrclo mencionado anteriormente (para Ubuntu)
Jordan Gee
Usé /root/.bashrc para centos.
schmudu
EJECUTAR echo "source /yourscript.bash" >> /etc/bash.bashrc hace el truco. si está utilizando ros dentro de estibador y desea configurar el entorno de esto es lo que debe hacer
user27221
17

De acuerdo con https://docs.docker.com/engine/reference/builder/#run, el shell predeterminado [Linux] RUNes /bin/sh -c. Parece que espera bashismos, por lo que debe utilizar la "forma ejecutiva" de RUNpara especificar su shell.

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

De lo contrario, usar la "forma de shell" de RUN y especificar un shell diferente da como resultado shells anidados.

# 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 tiene más de 1 comando que necesita un shell diferente, debe leer https://docs.docker.com/engine/reference/builder/#shell y cambiar su shell predeterminado colocando esto antes de sus comandos RUN:

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

Finalmente, si ha colocado algo en el .bashrcarchivo del usuario raíz que necesita, puede agregar el -lindicador al comando SHELLo RUNpara convertirlo en un shell de inicio de sesión y asegurarse de que se obtenga.

Nota: He ignorado intencionalmente el hecho de que no tiene sentido obtener un script como único comando en un EJECUTAR.

Bruno Bronosky
fuente
1
SHELL ["/bin/sh", "-c", "-l"]por lo que obtiene ~ / .bashrc, etc. en caso de que tenga configuraciones de entorno desde el contenedor base
MortenB
1
@MortenB, pero especificó (¿tipeado?) /bin/shQue no resolverá el problema de que bash no se use. Además, cuando se realiza una acción docker build, es poco probable que haya algo útil en el .bashrc del usuario raíz que necesite. Pero, si pones algo allí antes en el Dockerfile (como tal vez un JAVA_HOME, entonces sí. Pondré una nota al respecto en mi respuesta.)
Bruno Bronosky
Lo siento por el error tipográfico, estoy usando pyenv que necesita obtener ~ / .bashrc para establecer las rutas para la versión correcta de Python en mis imágenes base. Esto me hace usar cualquier base de Linux que haya y con dos líneas agrego cualquier versión en Python. Como python 3.7 en ubuntu16.04 donde base python es 3.5.2
MortenB
11

De acuerdo con la documentación de Docker

Para usar un shell diferente, que no sea '/ bin / sh', use la forma ejecutiva que pasa en el shell deseado. Por ejemplo,

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

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

Gianluca Casati
fuente
Esta es la respuesta correcta REAL. El autor de la respuesta seleccionada stackoverflow.com/a/25086628/117471 parece haber leído solo el primer ejemplo en la documentación a la que se vincula. No parecen haber leído el siguiente párrafo, que es lo que usted ha citado.
Bruno Bronosky
4

Si tiene SHELLdisponible, debe ir con esta respuesta : no use la aceptada, lo que lo obliga a colocar el resto del dockerfile en un comando por este comentario .

Si está utilizando una versión antigua de Docker y no tiene acceso a ella SHELL, esto funcionará siempre que no necesite nada .bashrc(que es un caso raro en Dockerfiles):

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

Tenga en cuenta que -ise necesita para que bash lea el archivo rc en absoluto.

Mohan
fuente
3

Es posible que desee correr bash -vpara ver qué se obtiene.

Haría lo siguiente en lugar de jugar con enlaces simbólicos:

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

vimdude
fuente
3

También tuve problemas para ejecutar sourceen un Dockerfile

Esto funciona perfectamente bien para construir el contenedor CentOS 6.6 Docker, pero dio problemas en los contenedores Debian

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

Así es como lo aborde, puede que no sea una forma elegante, pero esto es lo que funcionó para mí

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

Esto podría estar sucediendo porque sourceestá integrado en bash en lugar de un binario en algún lugar del sistema de archivos. ¿Su intención para el script que está buscando para alterar el contenedor después?

Paul Morie
fuente
1
El script actualiza el contenedor, pero para ser sincero, estaba tratando de hacer algo que no tenía sentido, así que he eludido el problema.
Hugo Rodger-Brown
1

Terminé poniendo mis cosas env .profiley mutado SHELLalgo como

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
fuente
3
"-c" debe ser el último argumento (antes de que se ejecute el comando)
peterk
0

Si solo está tratando de usar pip para instalar algo en virtualenv, puede modificar el PATH env para buscar primero en la carpeta bin de virtualenv

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

Luego, cualquier pip installcomando que siga en el Dockerfile encontrará primero / path / to / venv / bin / pip y lo usará, lo que se instalará en ese virtualenv y no en el sistema python.

shadfc
fuente