Usando a instrução RUN em um Dockerfile com ‘source’ não funciona

Eu tenho um Dockerfile que estou montando para instalar um ambiente python vanilla (no qual instalarei um aplicativo, mas em uma data 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 

A construção é executada 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 entrar nesse diretório (apenas para testar se as etapas anteriores foram confirmadas), posso ver que os arquivos existem como 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 comando source , recebo o mesmo erro ‘not found’ como acima. Se eu executar uma session de shell interativa, a origem 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, em seguida, felizmente acessar workon , mkvirtualenv etc.

Eu fiz algumas escavações, e inicialmente parecia que o problema poderia estar na diferença entre bash como o shell de login do Ubuntu, e traço como o shell do sistema Ubuntu, traço não suportando o comando de source .

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

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

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

Resposta Original

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

Isso deve funcionar para todas as imagens da base de encaixe do Ubuntu. Eu geralmente adiciono esta linha para cada Dockerfile que escrevo.

Editar por um espectador preocupado

Se você quiser obter o efeito de “usar bash vez de sh ao longo de todo o Dockerfile”, sem alterar e possivelmente danificar * o sistema operacional dentro do contêiner, basta informar ao Docker sua intenção . Isso é feito assim:

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

* O possível dano é que muitos scripts no Linux (em um novo Ubuntu, instalar grep -rHInE '/bin/sh' / retorna mais de 2700 resultados) espera um shell POSIX completo em /bin/sh . A shell bash não é apenas POSIX mais builtins extras. Existem builtins (e mais) que se comportam totalmente diferentes do que aqueles em POSIX. Eu apoio totalmente evitando POSIX (e a falácia de que qualquer script que você não testou em outro shell vai funcionar porque você acha que você evitou basmisms) e apenas usando o bashismo. Mas você faz isso com uma boa declaração em seu roteiro. Não puxando o shell POSIX de debaixo do sistema operacional inteiro. (A menos que você tenha tempo de verificar todos os 2700 scripts que acompanham o Linux, além de todos os pacotes que você instalar.)

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

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

A maneira mais simples é usar o operador dot no lugar de source, que é o equivalente sh do comando bash source :

Ao invés de:

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

Usar:

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

Verifique o comando SHELL . O shell padrão no Linux é [“/ bin / sh”, “-c”]

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

Você pode alterar o shell padrão usando SHELL que muda o shell usado para instruções RUN subsequentes no Dockerfile

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

Agora, o shell padrão foi alterado 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 opção --login que iniciaria um shell de login. Isso significa que ~/.bachrc por exemplo, seria lido e você não precisa fornecer isso explicitamente antes do seu comando.

Com base nas respostas desta página, gostaria de acrescentar que você deve estar ciente de que cada instrução RUN é executada independentemente das outras com /bin/sh -c e, portanto, não obterá vars de ambiente que normalmente seriam originados em shells de login.

A melhor maneira que encontrei até agora é adicionar o script ao /etc/bash.bashrc e invocar cada comando como bash login.

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

Você poderia, por exemplo, instalar e configurar o virtualenvwrapper, criar o env virtual, ativá-lo quando usar um login do bash e, em seguida, instalar seus módulos do python nesse 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 ..." 

Ler o manual nos arquivos de boot do bash ajuda a entender o que é originado quando.

Se você estiver usando o Docker 1.12 ou mais recente, use 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 o formulário shell dos comandos seja substituído. O shell padrão no Linux é [“/ bin / sh”, “-c”], e no Windows é [“cmd”, “/ S”, “/ C”]. A instrução SHELL deve ser escrita em formato JSON em um Dockerfile.

A instrução SHELL é particularmente útil no Windows, onde há duas camadas nativas comumente usadas e bem 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 seguintes instruções podem ser afetadas pela instrução SHELL quando o formulário de shell delas é usado 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 invocado pelo docker será:

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

Isso é ineficiente por dois motivos. Primeiro, há um processador de comandos cmd.exe desnecessário (também conhecido como shell) sendo chamado. Segundo, cada instrução RUN no formato shell requer um comando powershell extra que prefixará o comando.

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

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

Embora o formulário JSON não seja ambíguo e não use o cmd.exe desnecessário, ele requer mais detalhamento por meio de citações duplas e escape. O mecanismo alternativo é usar a instrução SHELL e o formulário shell, criando uma syntax mais natural para os usuários do Windows, especialmente quando combinada com a diretiva do analisador 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 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 de variável de ambiente atrasada pode ser modificada.

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

O recurso SHELL foi adicionado ao Docker 1.12.

De acordo com a documentação do Docker

Para usar um shell diferente, diferente de ‘/ bin / sh’, use o formulário exec passando o shell desejado. Por exemplo,

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

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

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 bashisms, então você deve usar o “exec mode” do RUN para especificar seu shell.

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

Caso contrário, usar o “shell form” do RUN e especificar um shell diferente resulta em shells nesteds.

 # 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ê tiver mais de um comando que precisa de um shell diferente, leia https://docs.docker.com/engine/reference/builder/#shell e altere seu shell padrão colocando isso antes de seus comandos RUN:

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

Finalmente, se você tiver colocado qualquer coisa no arquivo .bashrc do usuário root que precisar, poderá adicionar o sinalizador -l ao comando SHELL ou RUN para torná-lo um shell de login e garantir que ele seja originado.

Nota: Ignorei intencionalmente o fato de que é inútil criar um script como o único comando em um RUN.

Eu também tive problemas em executar source em um Dockerfile

Isso funciona perfeitamente para criar o contêiner do Docker do CentOS 6.6, mas deu problemas nos contêineres do 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 

Você pode querer executar bash -v para ver o que está sendo originado.

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

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

Isso pode estar acontecendo porque o source é um built-in para bash ao invés de um binário em algum lugar no sistema de arquivos. Sua intenção para o script que você está tentando alterar o contêiner depois disso?

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

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

Então, qualquer comando de pip install que siga no Dockerfile encontrará / path / to / venv / bin / pip primeiro e usará isso, que será instalado naquele virtualenv e não no sistema python.

Eu acabei colocando minhas coisas de env em .profile e mudei SHELL algo 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