关于bash:使用带有’source’的Dockerfile中的RUN指令不起作用

Using the RUN instruction in a Dockerfile with 'source' does not work

我有一个dockerfile,我正在组装它来安装一个普通的python环境(我将在其中安装一个应用程序,但在稍后的日期)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

生成运行正常,直到最后一行,在此我得到以下异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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

如果我将cx1〔0〕导入该目录(只是为了测试前面的步骤是否已提交),我可以看到文件按预期存在:

1
2
3
4
5
6
7
8
9
10
$ 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会话,那么source确实可以工作:

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

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

我做了一些挖掘,最初看起来问题可能在于bash作为Ubuntu登录shell和dash作为Ubuntu系统shell的区别,dash不支持source命令。

然而,对此的答案似乎是使用"."而不是source,但这只会导致docker运行时因一个go-panic异常而崩溃。

从dockerfile运行指令中运行shell脚本的最佳方法是什么(运行Ubuntu12.04LTS的默认基本映像)。


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


原始答案

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

这应该适用于每个Ubuntu Docker基本映像。我通常会为我写的每一个文档添加这一行。

由相关旁观者编辑

如果你想在整个dockerfile中使用bash而不是sh,在不改变和可能损坏容器内操作系统的情况下,你可以告诉docker你的意图。这样做:

1
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.)

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


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

1
2
3
4
5
6
7
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"

希望有帮助。


最简单的方法是使用点运算符代替source,它相当于bash source命令的sh值:

而不是:

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

用途:

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


RUN指令的默认shell是["/bin/sh","-c"]

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

使用shell指令,可以更改dockerfile中后续RUN指令的默认shell:

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

现在,默认shell已经更改,您不需要在每个运行指令中显式定义它。

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

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


基于本页的答案,我将补充说,您必须注意,每个run语句都独立于使用/bin/sh -c的其他语句运行,因此不会得到任何通常源于登录shell的环境变量。

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

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

例如,可以安装和设置virtualenvwrapper,创建虚拟env,在使用bash登录时激活它,然后将python模块安装到该env中:

1
2
3
4
5
6
7
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启动文件的手册有助于理解什么时候是源代码。


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

简短回答:

一般:

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

对于python vituralenv:

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

长回答:

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

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

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

The command invoked by docker will be:

1
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:

1
2
3
...
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:

1
2
3
4
5
6
7
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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.


根据http:/ / / / / docs.docker.com引擎参考/ # Builder运行默认的[ ]是/bin/sh -cRUNfor Linux。你是希望bashisms出现,所以你应该使用"exec"形式RUN学院指定的外壳。

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

否则,使用"壳形式和指定一个不同的'运行在嵌套壳壳的结果。

1
2
3
4
# 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"]

如果你有超过1个命令需要一个不同的壳,你应该读docs.docker.com http://////# Builder引擎参考壳壳放在改变你的默认运行在您的命令:

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

最后,如果你有什么经验,在根用户的文件,你需要.bashrc,你可以添加一个或-lSHELLRUN命令让它登录确保它得到壳源。

注:我有故意忽视这样的事实,它是一个开源的脚本不运行在一个唯一的指挥。


根据Docker文件

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

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

请参见https://docs.docker.com/engine/reference/builder/运行


我在运行dockerfile中的EDOCX1[0]时也遇到了问题。

这对于构建Centos6.6 Docker容器来说非常好,但在Debian容器中给出了问题。

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

这就是我解决问题的方式,也许不是一种优雅的方式,但这对我来说是有用的。

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

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

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

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


这可能是因为source是一个内置的bash,而不是文件系统中的某个二进制文件。您是否打算在随后修改容器的脚本?


我来把我的东西在.profileenv和mutated SHELL什么样

1
2
3
4
5
6
7
8
9
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


如果你只是想用pip在virtualenv中安装一些东西,你可以先修改路径env来查找virtualenv的bin文件夹。

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

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