El uso de la instrucción RUN en un Dockerfile con 'fuente' no funciona

343

Tengo un Dockerfile que estoy armando para instalar un entorno vanilla Python (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 se ejecuta 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 ejecutar el sourcecomando, aparece 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 investigado 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 , 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 RUN de Dockerfile para evitar esto (estoy ejecutando la imagen base predeterminada para Ubuntu 12.04 LTS)?

0
365

Editar: Mi respuesta es obsoleta. Creo que otra respuesta es mejor.

EJECUTAR / bin / bash -c "fuente /usr/local/bin/virtualenvwrapper.sh"

5
  • 77
    ¿Eh? Si obtiene un script dentro de un shell que solo existe para el comando, no puede tener un efecto duradero en ningún comando que se ejecute en el futuro, asumiendo que la suma total de su acción es establecer variables de entorno. Entonces, ¿por qué usarías sourceen absoluto, en lugar de solo bash /usr/local/bin/virtualenvwrapper.sh, en ese caso? Charles Duffy 21/06/2016 a las 13:31
  • 15
    RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"Leo 1 de septiembre de 2016 a las 2:28
  • 31
    Esto no es correcto aunque funciona. Lea docs.docker.com/engine/reference/builder/#run y no se detenga después de la segunda muestra de código. Lea la Nota: que sigue inmediatamente. Como /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"]. Debes seguir adelante y usar la "forma ejecutiva" de RUN para que puedas sacar la shsalida como talRUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]Bruno Bronosky 13/07/2017 a las 16:50
  • 8
    Consulte stackoverflow.com/a/45087082/117471 para comprender por qué esto crea un bashanidado en un shy, por lo tanto, debe evitarse. Bruno Bronosky 13/07/2017 a las 17:10
  • ¿por qué ENTRYPOINT [ “/bin/bash”, “-c”, “source activate pytorch-py35” ]no funciona? Charlie Parker 6 oct 2017 a las 18:58
176

Respuesta original

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

Esto debería funcionar para todas las imágenes base de la ventana acoplable 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"]

* The possible damage is that many scripts in Linux (on a fresh Ubuntu install grep -rHInE '/bin/sh' / returns over 2700 results) expect a fully POSIX shell at /bin/sh. The bash shell isn't just POSIX plus extra builtins. There are builtins (and more) that behave entirely different than those in POSIX. I FULLY support avoiding POSIX (and the fallacy that any script that you didn't test on another shell is going to work because you think you avoided basmisms) and just using bashism. But you do that with a proper shebang in your script. Not by pulling the POSIX shell out from under the entire OS. (Unless you have time to verify all 2700 plus scripts that come with Linux plus all those in any packages you install.)

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

3
  • 18
    Esto se puede simplificar un poco:ln -snf /bin/bash /bin/shapottere 30/09/15 a las 20:41
  • 28
    ln -s /bin/bash /bin/shesta es una idea terrible. ubuntu apunta / bin / sh a correr por una razón. dash es un shell completamente posix que es órdenes de magnitud más rápido que bash. vincular / bin / sh a bash reducirá drásticamente el rendimiento de su servidor. citar: wiki.ubuntu.com/DashAsBinShxero 9 de mayo de 2016 a las 18:35
  • 8
    Este es un truco sucio, no una solución. Si su script está siendo ejecutado por el shshell, pero lo desea bash, la solución adecuada es hacer que el shproceso se invoque bashcomo único, por ejemplo bash -c 'source /script.sh && …', o incluso podría ir tan lejos como para 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 secuencia de comandos es ejecutable (no solo fuente), nunca haga que su secuencia de comandos mienta en un #!/bin/shshebang si en realidad no es compatible con sh. Úselo en su #!/bin/bashlugar. Mark G. 13 de septiembre de 2016 a las 13:56
97

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

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

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

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

Ahora, el shell predeterminado ha cambiado y no es necesario 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 una --loginopción que iniciaría un shell de inicio de sesión. Esto significa que, ~/.bachrcpor ejemplo, se leerá y no es necesario que lo obtenga explícitamente antes de su comando

0
48

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.

0
48

La forma más sencilla es usar el operador de punto en lugar de la fuente, que es el equivalente sh del sourcecomando bash :

En lugar de:

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

Usar:

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

Si está utilizando Docker 1.12 o una versión más reciente, ¡simplemente use 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"]

The SHELL instruction allows the default shell used for the shell form of commands to be overridden. The default shell on Linux is ["/bin/sh", "-c"], and on Windows is ["cmd", "/S", "/C"]. The SHELL instruction must be written in JSON form in a Dockerfile.

The SHELL instruction is particularly useful on Windows where there are two commonly used and quite different native shells: cmd and powershell, as well as alternate shells available including sh.

The SHELL instruction can appear multiple times. Each SHELL instruction overrides all previous SHELL instructions, and affects all subsequent instructions. For example:

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

The following instructions can be affected by the SHELL instruction when the shell form of them is used in a Dockerfile: RUN, CMD and ENTRYPOINT.

The following example is a common pattern found on Windows which can be streamlined by using the SHELL instruction:

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

The command invoked by docker will be:

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

This is inefficient for two reasons. First, there is an un-necessary cmd.exe command processor (aka shell) being invoked. Second, each RUN instruction in the shell form requires an extra powershell -command prefixing the command.

To make this more efficient, one of two mechanisms can be employed. One is to use the JSON form of the RUN command such as:

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

While the JSON form is unambiguous and does not use the un-necessary cmd.exe, it does require more verbosity through double-quoting and escaping. The alternate mechanism is to use the SHELL instruction and the shell form, making a more natural syntax for Windows users, especially when combined with the escape parser directive:

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

Resulting in:

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>

The SHELL instruction could also be used to modify the way in which a shell operates. For example, using SHELL cmd /S /C /V:ON|OFF on Windows, delayed environment variable expansion semantics could be modified.

The SHELL instruction can also be used on Linux should an alternate shell be required such as zsh, csh, tcsh and others.

The SHELL feature was added in Docker 1.12.

23

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á ninguna variable de entorno que normalmente se obtendría 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 de bash.

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

Por ejemplo, podría instalar y configurar virtualenvwrapper, crear el entorno virtual, hacer que se active 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 qué se obtiene y cuándo.

0
21

Según https://docs.docker.com/engine/reference/builder/#run, el shell predeterminado de [Linux] RUNes /bin/sh -c. Parece estar esperando bashismos, por lo que debe usar 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 colocándolo 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 la -lbandera al comando SHELLor 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 RUN.

0
11

Según la documentación de Docker

To use a different shell, other than ‘/bin/sh’, use the exec form passing in the desired shell. For example,

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

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

0
5

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

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

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

Tenga en cuenta que -ies necesario para que bash lea el archivo rc.

4

También tuve problemas para ejecutar sourceen un Dockerfile

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

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

Así es como lo abordé, 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
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

2

Esto podría estar sucediendo porque sourceestá integrado en bash en lugar de ser un binario en algún lugar del sistema de archivos. ¿Es su intención que el script que está obteniendo altere el contenedor después?

0
1

Terminé poniendo mis cosas env .profiley muté 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
0
0

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

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

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

0

Aquí hay un ejemplo de Dockerfile que aprovecha varias técnicas inteligentes para que todos ejecuten un entorno conda completo para cada RUNestrofa. Puede utilizar un enfoque similar para ejecutar cualquier preparación arbitraria en un archivo de secuencia de comandos.

Nota: hay muchos matices cuando se trata de shells, señales de inicio de sesión / interactivos versus no login / no interactivos exec, la forma en que se manejan múltiples argumentos , las citas , cómo interactúan CMD y ENTRYPOINT , y un millón de otras cosas, así que no lo haga desanimado si al piratear con estas cosas, las cosas se desvían. He pasado muchas horas frustrantes investigando todo tipo de literatura y todavía no entiendo cómo funciona todo.

## Conda with custom entrypoint from base ubuntu image
## Build with e.g. `docker build -t monoconda .`
## Run with `docker run --rm -it monoconda bash` to drop right into
## the environment `foo` !
FROM ubuntu:18.04

## Install things we need to install more things
RUN apt-get update -qq &&\
    apt-get install -qq curl wget git &&\
    apt-get install -qq --no-install-recommends \
        libssl-dev \
        software-properties-common \
    && rm -rf /var/lib/apt/lists/*

## Install miniconda
RUN wget -nv https://repo.anaconda.com/miniconda/Miniconda3-4.7.12-Linux-x86_64.sh -O ~/miniconda.sh && \
    /bin/bash ~/miniconda.sh -b -p /opt/conda && \
    rm ~/miniconda.sh && \
    /opt/conda/bin/conda clean -tipsy && \
    ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh

## add conda to the path so we can execute it by name
ENV PATH=/opt/conda/bin:$PATH

## Create /entry.sh which will be our new shell entry point. This performs actions to configure the environment
## before starting a new shell (which inherits the env).
## The exec is important! This allows signals to pass
RUN     (echo '#!/bin/bash' \
    &&   echo '__conda_setup="$(/opt/conda/bin/conda shell.bash hook 2> /dev/null)"' \
    &&   echo 'eval "$__conda_setup"' \
    &&   echo 'conda activate "${CONDA_TARGET_ENV:-base}"' \
    &&   echo '>&2 echo "ENTRYPOINT: CONDA_DEFAULT_ENV=${CONDA_DEFAULT_ENV}"' \
    &&   echo 'exec "[email protected]"'\
        ) >> /entry.sh && chmod +x /entry.sh

## Tell the docker build process to use this for RUN.
## The default shell on Linux is ["/bin/sh", "-c"], and on Windows is ["cmd", "/S", "/C"]
SHELL ["/entry.sh", "/bin/bash", "-c"]
## Now, every following invocation of RUN will start with the entry script
RUN     conda update conda -y

## Create a dummy env
RUN     conda create --name foo

## I added this variable such that I have the entry script activate a specific env
ENV CONDA_TARGET_ENV=foo

## This will get installed in the env foo since it gets activated at the start of the RUN stanza
RUN  conda install pip

## Configure .bashrc to drop into a conda env and immediately activate our TARGET env
RUN conda init && echo 'conda activate "${CONDA_TARGET_ENV:-base}"' >>  ~/.bashrc
ENTRYPOINT ["/entry.sh"]
0

Me he enfrentado a un escenario similar para una aplicación desarrollada con el marco web web Django y estos son los pasos que funcionaron perfectamente para mí:

  • contenido de mi Dockerfile
[[email protected] project_textile]$ cat docker/Dockerfile.debug 
FROM malazo/project_textile_ubuntu:latest 

ENV PROJECT_DIR=/proyectos/project_textile PROJECT_NAME=project_textile WRAPPER_PATH=/usr/share/virtualenvwrapper/virtualenvwrapper.sh

COPY . ${PROJECT_DIR}/
WORKDIR ${PROJECT_DIR}

RUN echo "source ${WRAPPER_PATH}" > ~/.bashrc
SHELL ["/bin/bash","-c","-l"]
RUN     mkvirtualenv -p $(which python3) ${PROJECT_NAME} && \
        workon ${PROJECT_NAME} && \
        pip3 install -r requirements.txt 

EXPOSE 8000

ENTRYPOINT ["tests/container_entrypoint.sh"]
CMD ["public/manage.py","runserver","0:8000"]

  • contenido del archivo ENTRYPOINT " tests / container_entrypoint.sh ":
[[email protected] project_textile]$ cat tests/container_entrypoint.sh
#!/bin/bash
# *-* encoding : UTF-8 *-*
sh tests/deliver_env.sh
source ~/.virtualenvs/project_textile/bin/activate 
exec python "[email protected]"

  • finalmente, la forma en que implementé el contenedor fue:
[[email protected] project_textile]$ cat ./tests/container_deployment.sh 
#!/bin/bash

CONT_NAME="cont_app_server"
IMG_NAME="malazo/project_textile_app"
[ $(docker ps -a |grep -i ${CONT_NAME} |wc -l) -gt 0 ] && docker rm -f ${CONT_NAME} 
docker run --name ${CONT_NAME} -p 8000:8000 -e DEBUG=${DEBUG} -e MYSQL_USER=${MYSQL_USER} -e MYSQL_PASSWORD=${MYSQL_PASSWORD} -e MYSQL_HOST=${MYSQL_HOST} -e MYSQL_DATABASE=${MYSQL_DATABASE} -e MYSQL_PORT=${MYSQL_PORT}  -d ${IMG_NAME}

Realmente espero que esto sea útil para alguien más.

Saludos,

0

Tuve el mismo problema. Si también usa una imagen base de Python, puede cambiar la línea shebang en su script de shell a #!/bin/bash. Ver por ejemplo el container_entrypoint.shde Manuel Lazo .