在带有'source'的Dockerfile中使用RUN指令不起作用


274

我有一个Dockerfile,我正在将其安装在一起以安装香草python环境(我将在其中安装应用程序,但稍后再安装)。

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

构建运行正常,直到最后一行,我得到以下异常:

[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

如果我ls进入该目录(只是为了测试是否已完成前面的步骤),我可以看到文件按预期存在:

$ 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

如果我尝试仅运行source命令,则会收到与上述相同的“未找到”错误。但是,如果我运行一个交互式shell会话,则源代码确实可以工作:

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

我可以从这里运行脚本,然后愉快地访问workonmkvirtualenv等等。

我做了一些挖掘,最初看起来好像问题可能出在bash作为Ubuntu 登录shell破折号作为Ubuntu 系统shell之间的区别,破折号不支持该source命令。

但是,答案似乎是使用“。”。而不是source,但这只会导致Docker运行时因发生紧急恐慌而崩溃。

从Dockerfile RUN指令运行Shell脚本来解决此问题的最佳方法是什么(这是运行Ubuntu 12.04 LTS的默认基础映像的最佳方法)。


2
因此,不要“获取”它,只需运行命令即可。或者专门使用“ bash”运行shell脚本。
Alister Bulman 2013年

尝试过-尽管脚本不会失败,但我无法访问各种命令,而这正是我想要的。这个问题是同一回事-github.com/dotcloud/docker/issues/2847
Hugo Rodger-Brown

2
考虑到这一点,这是正确的。在容器环境中,Virtualenvwrapper可能毫无意义。我将取消它,而是使用“本地” virtualenv。
雨果·罗杰·布朗

1
解决这个更根本的办法就是stackoverflow.com/questions/4732200/...
拉夫欧嘉

试试CMD source activate django-py35
Belter

Answers:


315

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


66
??如果您在仅针对该命令存在的shell内提供脚本,则假定该脚本的操作总和是在设置环境变量,则该脚本不会对以后运行的任何命令产生持久影响。那么,在这种情况下source,为什么要使用全部而不是仅仅使用bash /usr/local/bin/virtualenvwrapper.sh
查尔斯·达菲

13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
狮子座

29
即使有效,这也不正确。阅读docs.docker.com/engine/reference/builder/#run,不要在第二个代码示例之后停下来。阅读注意:紧随其后。由于/bin/sh -c是默认外壳程序,因此RUN的此“外壳程序形式”转换为RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]。你应该继续使用RUN的“EXEC形式”这样你就可以把sh出像这样RUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
布鲁诺Bronosky

8
请参阅stackoverflow.com/a/45087082/117471以了解为什么这会bashsh和中创建嵌套,因此应避免使用。
布鲁诺·布鲁诺斯基


150

原始答案

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

这应该适用于每个Ubuntu docker基本映像。我通常为我编写的每个Dockerfile添加此行。

由相关旁观者编辑

如果您想获得“使用bash而不是sh在整个Dockerfile中使用”的效果,而又不改变可能损坏容器内的OS,您可以告诉Docker您的意图。这样完成:

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

*可能的损害是Linux 中的许多脚本(在全新的Ubuntu安装上grep -rHInE '/bin/sh' /返回的结果超过2700个)期望使用完整的POSIX shell /bin/sh。bash外壳不仅是POSIX加上额外的内置函数。有一些内置的(和更多的)行为与POSIX中的行为完全不同。我完全支持避免POSIX(而且谬论是,您未在另一个shell上测试过的任何脚本都将起作用,因为您认为您避免了basmisms),而只是使用了bashism。但是您可以在脚本中使用适当的shebang来实现。不是通过从整个操作系统中拉出POSIX shell来实现的。(除非您有时间验证Linux随附的所有2700 plus脚本以及安装的任何软件包中的所有脚本。)

下面的答案中有更多详细信息。https://stackoverflow.com/a/45087082/117471


18
可以简化一下:ln -snf /bin/bash /bin/sh
apottere

2
@ user1442219这将默认的命令解释器从替换shbash
Bhargav Nanekalva,2015年

27
ln -s /bin/bash /bin/sh这是一个可怕的想法。ubuntu将/ bin / sh设为破折号是有原因的。破折号是完全posix的外壳,比bash快几个数量级。将/ bin / sh链接到bash将大大降低服务器的性能。引用:wiki.ubuntu.com/DashAsBinSh
xero

7
这是一个肮脏的技巧,而不是解决方案。如果您的脚本是由shshell 运行的,但是您希望这样做bash,则正确的解决方案是一次性将sh进程bash作为一次调用,例如bash -c 'source /script.sh && …'或者甚至可以source完全避免bashisms(例如),而改为选择仅使用有效的POSIX等效项,例如. /script.sh。(请在空格后面加上空格.!)最后,如果脚本是可执行文件(不仅是可源代码),请不要使脚本与#!/bin/shshebang 说谎(如果它实际上与sh不兼容)。使用#!/bin/bash代替。
Mark G.

7
现在,我该如何对原始答案进行投票,并通过“有关人员”对所做的修改进行投票?
斯拉瓦

65

RUN指令的默认外壳为["/bin/sh", "-c"]

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

使用SHELL指令,您可以RUN在Dockerfile中为后续指令更改默认shell :

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

现在,默认外壳已更改,您无需在每个RUN指令中都明确定义它

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

附加说明:您还可以添加--login启动登录外壳程序的选项。~/.bachrc例如,这意味着将要读取,并且您不需要在命令前显式地获取它


1
使用的绝佳指针--login-自己自己弄清楚
mattexx

通过使用,SHELL ["/bin/bash", "-c", "-l"] 我可以对.bashrc文件使用进一步的更新,这使我可以轻松地运行asdf命令。
Rowinson Gallego

46

我有同样的问题,为了在virtualenv内执行pip install我必须使用以下命令:

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"

希望对您有所帮助。


如果您来自ROS答案,是的。像这样的东西:RUN /bin/bash -c "source /opt/ros/melodic/setup.bash && \ cd /home && \ git clone https://angelos.p:$password@gitlab.com/inno/grpc-comms.git && \ cd grpc-comms && \ mkdir build && \ cd build && \ cmake .. && make"
angelos.p

44

最简单的方法是使用点运算符代替source,这与bash source命令的sh等效:

代替:

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

用:

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

“ source是内置的bourne shell和POSIX'special '内置” – ss64.com/bash/source.html linux.die.net/man/1/sh ... . / source在文件名之后还接受位置参数
Wes Turner

5
这是行不通的,因为每个RUN命令都独立工作。RUN命令完成后,来自source或的更改.将丢失。请参阅:stackoverflow.com/a/40045930/19501
阿米特(Amit)

26

如果您使用的是Docker 1.12或更高版本,请使用SHELL

简短答案:

一般:

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

对于python vituralenv:

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

长答案:

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

SHELL ["executable", "parameters"]

SHELL指令允许覆盖用于命令的shell形式的默认shell。在Linux上,默认外壳程序为[“ / bin / sh”,“ -c”],在Windows上,默认外壳程序为[“ cmd”,“ / S”,“ / C”]。必须在Dockerfile中以JSON形式编写SHELL指令。

SHELL指令在Windows上特别有用,在Windows上有两个常用且完全不同的本机shell:cmd和powershell,以及包括sh在内的备用shell。

SHELL指令可以出现多次。每个SHELL指令将覆盖所有先前的SHELL指令,并影响所有后续的指令。例如:

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

在Dockerfile中使用它们的外壳形式时,以下指令可能会受到SHELL指令的影响:RUN,CMD和ENTRYPOINT。

以下示例是Windows上常见的模式,可以使用SHELL指令进行精简:

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

docker调用的命令将是:

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

这效率低下有两个原因。首先,有一个不必要的cmd.exe命令处理器(又名Shell)被调用。其次,shell形式的每个RUN指令都需要一个额外的powershell -command作为该命令的前缀。

为了使其更有效,可以采用两种机制之一。一种是使用RUN命令的JSON形式,例如:

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

尽管JSON形式是明确的,并且不使用不必要的cmd.exe,但它确实需要通过双引号和转义来实现更多的详细信息。另一种机制是使用SHELL指令和shell形式,从而使Windows用户的语法更加自然,尤其是与转义解析器指令结合使用时:

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

导致:

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>

SHELL指令还可用于修改外壳程序的运行方式。例如,在Windows上使用SHELL cmd / S / C / V:ON | OFF,可以修改延迟的环境变量扩展语义。

如果需要备用shell(例如zsh,csh,tcsh等),也可以在Linux上使用SHELL指令。

SHELL功能已在Docker 1.12中添加。


20

在此页面上的答案的基础上,我要补充一点,您必须注意,每个RUN语句都独立于其他RUN语句运行/bin/sh -c,因此不会获得通常来自登录Shell的任何环境var。

到目前为止,我发现的最好方法是将脚本添加到/etc/bash.bashrcbash登录中,然后调用每个命令。

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

例如,您可以安装和设置virtualenvwrapper,创建虚拟环境,使用bash登录时将其激活,然后将python模块安装到此环境中:

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

阅读有关bash启动文件的手册可帮助您了解何时提供了什么。


1
不错,根据您的解决方案,我只是想说明一下: ADD env-file /etc/profile.d/installerenv.sh RUN /bin/bash --login -c 'env' RUN /bin/bash -c 'rm /etc/profile.d/installerenv.sh' 如果一个用例向我的docker build透视图添加了更多的inject环境变量,我建议您看一下docs.docker.com/compose/yml /#env文件
daniel.kahlenberg

1
我认为,这样做的问题是,您最终不会缓存每个RUN命令的结果,这意味着您无法安装大量项目依赖项,然后复制源代码并利用Docker的中间步骤缓存。它将每次重新安装所有项目依赖项。
erewok

使用/etc/bashrc红帽,而不是/etc/bash.bashrc上面(的Ubuntu)作为提及
乔丹啧啧

我将/root/.bashrc用于centos。
schmudu

RUN echo“ source /yourscript.bash” >> /etc/bash.bashrc可以解决问题。如果您在docker内部使用ros并想要设置环境,这就是您应该做的
user27221

17

根据https://docs.docker.com/engine/reference/builder/#run的默认[Linux] shell RUN/bin/sh -c。您似乎期望接受bashisms,因此应使用“ exec形式” RUN来指定您的shell。

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

否则,使用RUN的“外壳形式”并指定其他外壳会导致嵌套外壳。

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

如果您有多个命令需要不同的shell,则应阅读https://docs.docker.com/engine/reference/builder/#shell并通过将其放在RUN命令之前来更改默认shell:

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

最后,如果您在根用户.bashrc文件中放置了所需的任何内容,则可以将-l标志添加到SHELLor RUN命令中,以使其成为登录shell,并确保其来源。

注意:我故意忽略了以下事实:将脚本作为RUN中的唯一命令是没有意义的。


SHELL ["/bin/sh", "-c", "-l"]因此,如果您从基本容器中获得环境设置,它将获取〜/ .bashrc等资源
MortenB

1
@MortenB,但是您指定了(输入错误?)/bin/sh这不能解决不使用bash的问题。另外,在执行操作时docker build,在根用户的.bashrc文件中几乎不需要任何有用的东西。但是,如果你把东西在里面早些时候在Dockerfile(譬如一个JAVA_HOME,然后是的,我就会把一张纸条它在我的答案。
布鲁诺Bronosky

很抱歉输入错误,我使用的是pyenv,它需要使用〜/ .bashrc来设置基本映像中正确python版本的路径。这使我可以使用任何存在的linux基础,并用两行代码在python上添加任何版本。像ubuntu16.04上的python 3.7,其中基本python是3.5.2
MortenB


4

如果SHELL有空的话,应该回答这个问题 - 不要使用接受的答案,这会迫使您根据此注释将其余的dockerfile放入一个命令中。

如果您使用的是旧版本的Docker并且无权访问SHELL,则只要您不需要任何东西.bashrc,它就可以工作(这在Dockerfiles中很少见):

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

请注意-i,使bash完全读取rcfile是必需的。


3

您可能需要运行bash -v以查看来源。

我将执行以下操作,而不是使用符号链接:

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


3

source在Dockerfile 中运行也遇到了问题

这对于构建CentOS 6.6 Docker容器运行得很好,但是在Debian容器中却出现了问题。

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

这就是我的解决方法,可能不是一种优雅的方法,但这对我有用

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

2

这可能是由于sourcebash内置的,而不是文件系统上某个位置的二进制文件而发生的。您是否打算在以后采购的脚本中更改容器?


1
该脚本确实更新了容器-但是老实说,我试图做一些没有意义的事情,所以绕过了这个问题。
雨果·罗杰·布朗

1

我最终把我的环境东西放了进去,.profile然后SHELL

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

3
“ -c”必须是最后一个参数(在command.to被执行之前)
peterk 18-10-7

0

如果您只是想使用pip将某些东西安装到virtualenv中,则可以修改PATH env以首先查看virtualenv的bin文件夹

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

然后pip install,Dockerfile中的所有命令将首先找到/ path / to / venv / bin / pip并使用该命令,该命令将安装到该virtualenv中,而不是系统python中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.