O uso da instrução RUN em um Dockerfile com 'source' não funciona

274

Eu tenho um Dockerfile que estou montando para instalar um ambiente vanilla python (no qual instalarei um aplicativo, mas posteriormente).

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

A compilação funciona bem até a última linha, onde recebo a seguinte exceção:

[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

Se eu lsentrar nesse diretório (apenas para testar se as etapas anteriores foram confirmadas), posso ver que os arquivos existem conforme o esperado:

$ 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

Se eu tentar apenas executar o sourcecomando, recebo o mesmo erro 'não encontrado' como acima. Se eu executar uma sessão de shell interativa, no entanto, a fonte funcionará:

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

Eu posso executar o script a partir daqui e acessar com prazer workon, mkvirtualenvetc.

Eu fiz algumas pesquisas e, inicialmente, parecia que o problema poderia estar na diferença entre o bash como o shell de login do Ubuntu e o dash como o shell do sistema Ubuntu , o dash não suportando o sourcecomando.

No entanto, a resposta para isso parece ser usar '.' em vez de source, mas isso apenas faz com que o tempo de execução do Docker exploda com uma exceção de pânico.

Qual é a melhor maneira de executar um shell script a partir de uma instrução RUN do Dockerfile para contornar isso (estou executando a imagem base padrão do Ubuntu 12.04 LTS).

Hugo Rodger-Brown
fonte
2
Portanto, não a 'fonte', basta executar o comando. Ou execute especificamente o shell script com 'bash'.
Alister Bulman
Tentei isso - embora o script não falhe, não tenho acesso aos vários comandos, o que eu queria. Esta questão é a mesma coisa - github.com/dotcloud/docker/issues/2847
Hugo Rodger-Brown
2
O que, pensando bem, está correto. O virtualenvwrapper provavelmente não faz sentido em um ambiente de contêiner. Vou fazer o backup e usar o virtualenv 'nativo'.
Hugo Rodger-Brown
1
A maneira mais fundamental de abordar isso é o stackoverflow.com/questions/4732200/…
Gaurav Ojha
TenteCMD source activate django-py35
Belter

Respostas:

315

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

chobo
fonte
67
Eh? Se você originar um script dentro de um shell que exista apenas para o comando, ele não poderá ter um efeito duradouro em nenhum comando futuro executado, supondo que a soma total de sua ação esteja configurando variáveis ​​de ambiente. Então, por que você usaria source, vs apenas bash /usr/local/bin/virtualenvwrapper.sh, nesse caso?
Charles Duffy #
13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
Leo
29
Isso não está correto, mesmo que funcione. Leia docs.docker.com/engine/reference/builder/#run e não pare após o segundo exemplo de código. Leia a nota: que se segue imediatamente. Como /bin/sh -cé o shell padrão, esse "formulário de shell" do RUN se traduz em RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]. Você deve seguir em frente e usar a "forma executiva" do RUN para que você possa shsair assim.RUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
Bruno Bronosky
8
Consulte stackoverflow.com/a/45087082/117471 para entender por que isso cria um bashaninhado em um she, portanto, deve ser evitado.
Bruno Bronosky
4
Uma resposta muito melhor está aqui: stackoverflow.com/a/42216046/1663462
Chris Stryczynski
150

Resposta original

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

Isso deve funcionar para todas as imagens base do docker do Ubuntu. Geralmente, adiciono essa linha a todos os Dockerfiles que escrevo.

Editar por um espectador preocupado

Se você deseja obter o efeito de "usar em bashvez de shtodo esse arquivo Dockerfile", sem alterar e possivelmente danificar * o sistema operacional dentro do contêiner, basta dizer à Docker sua intenção . Isso é feito assim:

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

* O possível dano é que muitos scripts no Linux (em uma nova instalação do Ubuntu grep -rHInE '/bin/sh' /retornam mais de 2700 resultados) esperam um shell totalmente POSIX em /bin/sh. O shell bash não é apenas o POSIX mais os recursos extras. Existem componentes internos (e mais) que se comportam totalmente diferentes dos do POSIX. Eu apoio totalmente a evitar o POSIX (e a falácia de que qualquer script que você não testou em outro shell funcionará porque você acha que evitou basmismos) e apenas usando o bashism. Mas você faz isso com um shebang adequado em seu script. Não puxando o shell POSIX para fora de todo o sistema operacional. (A menos que você tenha tempo para verificar todos os scripts 2700 plus que acompanham o Linux e todos os pacotes instalados).

Mais detalhes nesta resposta abaixo. https://stackoverflow.com/a/45087082/117471

Anubhav Sinha
fonte
18
Isso pode ser simplificado um pouco:ln -snf /bin/bash /bin/sh
apottere
2
@ user1442219 isso substitui o intérprete de comando padrão de shparabash
Bhargav Nanekalva 19/10/2015
27
ln -s /bin/bash /bin/shessa é uma péssima ideia. O Ubuntu direciona / bin / sh para correr por um motivo. dash é um shell totalmente posix, com ordens de magnitude mais rápidas que o bash. vincular / bin / sh ao bash reduzirá drasticamente o desempenho do seu servidor. citação: wiki.ubuntu.com/DashAsBinSh
xero
7
Este é um truque sujo, não uma solução. Se o seu script está sendo executado pelo shshell, mas você deseja bash, a solução adequada é fazer com que o shprocesso seja invocado bashcomo único, por exemplo bash -c 'source /script.sh && …', ou você pode até ir longe para evitar totalmente bashismos (como source) e, em vez disso, optar por sempre usar apenas equivalentes POSIX válidos, por exemplo . /script.sh. (Lembre-se do espaço após o .!) Por último, se o seu script for executável (não apenas fonte), nunca faça o seu script ficar com um #!/bin/shshebang se ele não for realmente compatível com sh. Use em #!/bin/bashvez disso.
Mark G.
7
E agora, como voto negativo na resposta original e votação positiva na edição de 'um interessado'?
quer
65

O shell padrão para a RUNinstrução é ["/bin/sh", "-c"].

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

Usando a instrução SHELL , você pode alterar o shell padrão para RUNinstruções subsequentes no Dockerfile:

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

Agora, o shell padrão mudou e você não precisa defini-lo explicitamente em todas as instruções RUN

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

Nota adicional : Você também pode adicionar a --loginopção que iniciaria um shell de login. Isso significa que, ~/.bachrcpor exemplo, seria lido e você não precisa fornecê-lo explicitamente antes do seu comando

Ahmad Abdelghany
fonte
1
Grande ponteiro sobre o uso --login- apenas descobriu isso sozinho
mattexx
1
Ao usar, SHELL ["/bin/bash", "-c", "-l"] eu pude usar outras atualizações no arquivo .bashrc, o que me permitiu executar comandos asdf facilmente.
Rowinson Gallego 13/03
46

Eu tive o mesmo problema e, para executar a instalação do pip dentro do virtualenv, tive 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 ajude.

Andrea Grandi
fonte
Caso você tenha vindo de respostas ROS, sim, isso funciona. Algo 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

A maneira mais simples é usar o operador de ponto no lugar da fonte, que é o equivalente sh do sourcecomando bash :

Ao invés de:

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

Usar:

RUN . /usr/local/bin/virtualenvwrapper.sh
mixja
fonte
"source é um builtin shell bourne e um POSIX` special 'builtin "- ss64.com/bash/source.html linux.die.net/man/1/sh ... . / sourcetambém aceita parâmetros posicionais após o nome do arquivo
Wes Turner
5
Isso não funciona, pois cada comando RUN funciona independentemente. As alterações sourceou .são perdidas quando o comando RUN é concluído. Veja: stackoverflow.com/a/40045930/19501
amit
26

Se você estiver usando o Docker 1.12 ou mais recente, basta usar SHELL!

Resposta curta:

geral:

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

para python vituralenv:

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

Resposta longa:

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

SHELL ["executable", "parameters"]

A instrução SHELL permite que o shell padrão usado para a forma de comandos do shell seja substituída. O shell padrão no Linux é ["/ bin / sh", "-c"] e no Windows é ["cmd", "/ S", "/ C"]. A instrução SHELL deve ser escrita no formato JSON em um Dockerfile.

A instrução SHELL é particularmente útil no Windows, onde existem dois shells nativos comumente usados ​​e bastante diferentes: cmd e powershell, além de shells alternativos disponíveis, incluindo sh.

A instrução SHELL pode aparecer várias vezes. Cada instrução SHELL substitui todas as instruções SHELL anteriores e afeta todas as instruções subseqüentes. Por exemplo:

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

As instruções a seguir podem ser afetadas pela instrução SHELL quando a forma do shell delas é usada em um Dockerfile: RUN, CMD e ENTRYPOINT.

O exemplo a seguir é um padrão comum encontrado no Windows que pode ser simplificado usando a instrução SHELL:

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

O comando chamado pelo docker será:

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

Isso é ineficiente por duas razões. Primeiro, há um processador de comando cmd.exe desnecessário (também conhecido como shell) sendo invocado. Segundo, cada instrução RUN na forma de shell requer um comando extra do powershell que prefixa o comando.

Para tornar isso mais eficiente, um dos dois mecanismos pode ser empregado. Uma é usar o formulário JSON do comando RUN, como:

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

Embora o formulário JSON seja inequívoco e não use o cmd.exe desnecessário, ele exige mais detalhamento por meio de aspas duplas e escape. O mecanismo alternativo é usar a instrução SHELL e a forma do shell, criando uma sintaxe mais natural para usuários do Windows, especialmente quando combinada com a diretiva de escape 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'

Resultando em:

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>

A instrução SHELL também pode ser usada para modificar a maneira como um shell opera. Por exemplo, usando SHELL cmd / S / C / V: ON | OFF no Windows, a semântica de expansão da variável de ambiente atrasada pode ser modificada.

A instrução SHELL também pode ser usada no Linux, caso seja necessário um shell alternativo, como zsh, csh, tcsh e outros.

O recurso SHELL foi adicionado no Docker 1.12.

Mithril
fonte
20

Com base nas respostas desta página, eu acrescentaria que você deve estar ciente de que cada instrução RUN é executada independentemente das outras /bin/sh -ce, portanto, não obterá nenhum ambiente de variáveis ​​que normalmente seriam originadas em shells de login.

A melhor maneira que encontrei até agora é adicionar o script /etc/bash.bashrce, em seguida, chamar cada comando como login do bash.

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

Você pode, por exemplo, instalar e configurar o virtualenvwrapper, criar o ambiente virtual, ativá-lo quando você usa um login bash e, em seguida, instalar seus módulos python nesse ambiente:

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

A leitura do manual nos arquivos de inicialização do bash ajuda a entender o que é originado quando.

TomDotTom
fonte
1
Legal, com base na sua solução, o que fiz apenas para ilustrar foi: ADD env-file /etc/profile.d/installerenv.sh RUN /bin/bash --login -c 'env' RUN /bin/bash -c 'rm /etc/profile.d/installerenv.sh' Se o caso de uso de alguém adiciona mais variáveis ​​de ambiente de injeção à perspectiva de construção do docker como a minha, recomendo consultar docs.docker.com/compose/yml / # env-file também.
Daniel.kahlenberg
1
O problema com isso, acredito, é que você não acaba armazenando em cache os resultados de cada RUNcomando, o que significa que não é possível instalar muitas dependências do projeto e, em seguida, copiar o código-fonte e tirar proveito dos benefícios de Armazenamento em cache da etapa intermediária do Docker. Ele reinstalará todas as dependências do projeto todas as vezes.
erewok
Use /etc/bashrcpara Redhat, em vez de /etc/bash.bashrc, como mencionado acima (Para Ubuntu)
Jordan Gee
Eu usei /root/.bashrc para centos.
21418 schmudu
EXECUTAR echo "source /yourscript.bash" >> /etc/bash.bashrc faz o truque. se você estiver usando ros dentro janela de encaixe e quiser configurar o ambiente é isso que você deve fazer
user27221
17

De acordo com https://docs.docker.com/engine/reference/builder/#run, o shell [Linux] padrão para RUNé /bin/sh -c. Você parece estar esperando basismos, então você deve usar o "exec form" de RUNpara especificar seu shell.

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

Caso contrário, usar o "formulário de shell" do RUN e especificar um shell diferente resulta em shells aninhados.

# 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"]

Se você possui mais de um comando que precisa de um shell diferente, leia https://docs.docker.com/engine/reference/builder/#shell e altere o shell padrão colocando-o antes dos comandos RUN:

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

Por fim, se você colocou algo no .bashrcarquivo do usuário raiz que precisa, pode adicionar o -lsinalizador ao comando SHELLou RUNpara torná-lo um shell de logon e garantir que ele seja originado.

Nota: Ignorei intencionalmente o fato de que não faz sentido originar um script como o único comando em um RUN.

Bruno Bronosky
fonte
1
SHELL ["/bin/sh", "-c", "-l"]então ele
origina
1
@ MortenB, mas você especificou (digitou?) /bin/shQue não resolverá o problema do bash não estar sendo usado. Além disso, ao fazer um docker buildé improvável que exista algo útil no .bashrc do usuário raiz que você precise. Mas, se você colocar alguma coisa lá no início do Dockerfile (como talvez um JAVA_HOME., Aí sim eu vou colocar uma nota sobre isso na minha resposta.
de Bruno Bronosky
Desculpe pelo erro de digitação, estou usando pyenv, que precisa usar o ~ / .bashrc para definir os caminhos para a versão correta do python nas minhas imagens de base. isso me faz usar qualquer base de linux existente e, com duas linhas, adicionar qualquer versão em python. Como python 3.7 no ubuntu16.04, em que python base é 3.5.2
MortenB
11

De acordo com a documentação do Docker

Para usar um shell diferente, diferente de '/ bin / sh', use o formulário exec que passa no shell desejado. Por exemplo,

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

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

Gianluca Casati
fonte
Esta é a resposta correta REAL. O autor da resposta selecionada stackoverflow.com/a/25086628/117471 parece ter lido apenas o primeiro exemplo da documentação à qual está vinculado. Eles não parecem ter lido o próximo parágrafo, que é o que você citou.
11786 Bruno Bronosky
4

Se você tiver SHELLdisponível, siga esta resposta - não use a aceita, o que força você a colocar o restante do arquivo docker em um comando por esse comentário .

Se você estiver usando uma versão antiga do Docker e não tiver acesso SHELL, isso funcionará desde que você não precise de nada .bashrc(o que é um caso raro no Dockerfiles):

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

Observe que -ié necessário fazer com que o bash leia o rcfile.

Mohan
fonte
3

Você pode querer correr bash -v para ver o que está sendo adquirido.

Eu faria o seguinte em vez de jogar com links simbólicos:

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

vimdude
fonte
3

Eu também tive problemas na execução source em um Dockerfile

Isso funciona perfeitamente para criar contêineres CentOS 6.6 Docker, mas deu problemas em contêineres Debian

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

É assim que eu lidei com isso, pode não ser uma maneira elegante, mas é isso que funcionou para mim

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

Isso pode estar acontecendo porque sourceé um built-in para bash em vez de um binário em algum lugar do sistema de arquivos. Sua intenção para o script que você está contratando é alterar o contêiner posteriormente?

Paul Morie
fonte
1
O script atualiza o contêiner - mas, para ser sincero, eu estava tentando fazer algo que não fazia sentido; portanto, contornei o problema.
Hugo Rodger-Brown
1

Acabei colocando minhas coisas env .profilee 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
fonte
3
"-c" necessidade de ser o último argumento (antes da command.to ser executado)
peterk
0

Se você está apenas tentando usar o pip para instalar algo no virtualenv, pode modificar o env PATH para procurar primeiro na pasta bin do virtualenv

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

Em seguida, quaisquer pip installcomandos que seguem na Dockerfile vai encontrar / path / to / venv / bin / pip primeiro e usar isso, que irá instalar em que virtualenv e não o sistema de python.

shadfc
fonte