bash 在带有“源”的 Dockerfile 中使用 RUN 指令不起作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20635472/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using the RUN instruction in a Dockerfile with 'source' does not work
提问by Hugo Rodger-Brown
I have a Dockerfile that I am putting together to install a vanilla python environment (into which I will be installing an app, but at a later date).
我有一个 Dockerfile,我将它放在一起来安装一个 vanilla 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
The build runs ok until the last line, where I get the following exception:
构建运行正常,直到最后一行,在那里我得到以下异常:
[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
If I ls
into that directory (just to test that the previous steps were committed) I can see that the files exist as expected:
如果我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
If I try just running the source
command I get the same 'not found' error as above. If I RUN an interactive shell session however, source does work:
如果我尝试只运行该source
命令,则会收到与上述相同的“未找到”错误。但是,如果我运行交互式 shell 会话,源代码确实有效:
$ docker run 3a87 bash
source
bash: line 1: source: filename argument required
source: usage: source filename [arguments]
I can run the script from here, and then happily access workon
, mkvirtualenv
etc.
我可以从这里运行脚本,然后愉快地访问workon
,mkvirtualenv
等等。
I've done some digging, and initially it looked as if the problem might lie in the difference between bashas the Ubuntu login shell, and dashas the Ubuntu system shell, dashnot supporting the source
command.
我已经做了一些挖掘,最初看起来问题可能在于bash作为 Ubuntu登录 shell和dash作为 Ubuntu系统 shell之间的区别,dash不支持该source
命令。
However, the answer to this appears to be to use '.'instead of source
, but this just causes the Docker runtime to blow up with a go panic exception.
但是,对此的答案似乎是使用“。” 而不是source
,但这只会导致 Docker 运行时因 go panic 异常而崩溃。
What is the best way to run a shell script from a Dockerfile RUN instruction to get around this (am running off the default base image for Ubuntu 12.04 LTS).
从 Dockerfile RUN 指令运行 shell 脚本来解决这个问题的最佳方法是什么(我正在运行 Ubuntu 12.04 LTS 的默认基本映像)。
回答by chobo
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh"
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh"
回答by Anubhav Sinha
Original Answer
原答案
FROM ubuntu:14.04
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
This should work for every Ubuntu docker base image. I generally add this line for every Dockerfile I write.
这应该适用于每个 Ubuntu docker 基础镜像。我通常为我编写的每个 Dockerfile 添加这一行。
Edit by a concerned bystander
由相关旁观者编辑
If you want to get the effect of "use bash
instead of sh
throughout this entire Dockerfile", without alteringand possibly damaging* the OS inside the container, you can just tell Docker your intention. That is done like so:
如果你想获得“使用bash
而不是sh
在整个 Dockerfile 中使用”的效果,而不改变和可能损坏容器内的操作系统,你可以告诉 Docker 你的意图。这样做是这样的:
SHELL ["/bin/bash", "-c"]
* The possible damage is that manyscripts 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.)
* 可能的损害是Linux中的许多脚本(在全新的 Ubuntu 安装上
grep -rHInE '/bin/sh' /
返回超过 2700 个结果)期望在/bin/sh
. bash shell 不仅仅是 POSIX 加上额外的内置函数。有一些内置函数(以及更多)的行为与 POSIX 中的内置函数完全不同。我完全支持避免使用 POSIX(以及你没有在另一个 shell 上测试的任何脚本都会工作的谬论,因为你认为你避免了 basmism)并且只使用 bashism。但是您可以在脚本中使用适当的shebang 来做到这一点。不是通过从整个操作系统下拉出 POSIX shell。(除非您有时间验证 Linux 附带的所有 2700 多个脚本以及您安装的任何软件包中的所有脚本。)
More detail in this answer below. https://stackoverflow.com/a/45087082/117471
下面这个答案的更多细节。https://stackoverflow.com/a/45087082/117471
回答by Ahmad Abdelghany
The default shell for the RUN
instruction is ["/bin/sh", "-c"]
.
RUN
指令的默认 shell是["/bin/sh", "-c"]
.
RUN "source file" # translates to: RUN /bin/sh -c "source file"
Using SHELL instruction, you can change default shell for subsequent RUN
instructions in Dockerfile:
使用SHELL 指令,您可以更改RUN
Dockerfile 中后续指令的默认 shell :
SHELL ["/bin/bash", "-c"]
Now, default shell has changed and you don't need to explicitly define it in every RUN instruction
现在,默认 shell 已更改,您无需在每个 RUN 指令中明确定义它
RUN "source file" # now translates to: RUN /bin/bash -c "source file"
Additional Note: You could also add --login
option which would start a login shell. This means ~/.bachrc
for example would be read and you don't need to source it explicitly before your command
附加说明:您还可以添加--login
选项来启动登录 shell。这意味着~/.bachrc
例如将被读取并且您不需要在您的命令之前明确地获取它
回答by Andrea Grandi
I had the same problem and in order to execute pip install inside virtualenv I had to use this command:
我遇到了同样的问题,为了在 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"
I hope it helps.
我希望它有帮助。
回答by mixja
Simplest way is to use the dot operator in place of source, which is the sh equivalent of the bash source
command:
最简单的方法是使用点运算符代替 source,它是 sh 等价于 bashsource
命令:
Instead of:
代替:
RUN source /usr/local/bin/virtualenvwrapper.sh
Use:
用:
RUN . /usr/local/bin/virtualenvwrapper.sh
回答by Mithril
If you are using Docker 1.12 or newer, just use SHELL
!
如果您使用的码头工人1.12或更高版本,只需使用SHELL
!
Short Answer:
简答:
general:
一般的:
SHELL ["/bin/bash", "-c"]
for python vituralenv:
对于 python vituralenv:
SHELL ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]
Long Answer:
长答案:
from https://docs.docker.com/engine/reference/builder/#/shell
来自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.
SHELL ["executable", "parameters"]
SHELL 指令允许覆盖用于命令的 shell 形式的默认 shell。Linux 上的默认 shell 是 ["/bin/sh", "-c"],Windows 上是 ["cmd", "/S", "/C"]。SHELL 指令必须以 JSON 格式写入 Dockerfile。
SHELL 指令在 Windows 上特别有用,Windows 有两种常用且截然不同的本机 shell:cmd 和 powershell,以及可用的备用 shell,包括 sh。
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 形式时,以下指令可能会受到 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 指令也可用于修改 shell 的运行方式。例如,在 Windows 上使用 SHELL cmd /S /C /V:ON|OFF,可以修改延迟的环境变量扩展语义。
如果需要备用 shell,例如 zsh、csh、tcsh 等,也可以在 Linux 上使用 SHELL 指令。
SHELL 功能是在 Docker 1.12 中添加的。
回答by TomDotTom
Building on the answers on this page I would add that you have to be aware that each RUN statement runs independently of the others with /bin/sh -c
and therefore won't get any environment vars that would normally be sourced in login shells.
基于此页面上的答案,我想补充一点,您必须意识到每个 RUN 语句都独立于其他语句运行/bin/sh -c
,因此不会获得通常在登录 shell 中获取的任何环境变量。
The best way I have found so far is to add the script to /etc/bash.bashrc
and then invoke each command as bash login.
到目前为止,我发现的最好方法是将脚本添加到/etc/bash.bashrc
,然后以 bash 登录名调用每个命令。
RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc
RUN /bin/bash --login -c "your command"
You could for instance install and setup virtualenvwrapper, create the virtual env, have it activate when you use a bash login, and then install your python modules into this env:
例如,您可以安装和设置 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 ..."
Reading the manual on bash startup fileshelps understand what is sourced when.
阅读有关bash 启动文件的手册有助于了解什么时候来源。
回答by Bruno Bronosky
According to https://docs.docker.com/engine/reference/builder/#runthe default [Linux] shell for RUN
is /bin/sh -c
. You appear to be expecting bashisms, so you should use the "exec form" of RUN
to specify your shell.
根据https://docs.docker.com/engine/reference/builder/#run默认的 [Linux] shellRUN
是/bin/sh -c
. 您似乎在期待 bashisms,因此您应该使用 of 的“exec 形式”RUN
来指定您的 shell。
RUN ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]
Otherwise, using the "shell form" of RUN and specifying a different shell results in nested shells.
否则,使用 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"]
If you have more than 1 command that needs a different shell, you should read https://docs.docker.com/engine/reference/builder/#shelland change your default shell by placing this before your RUN commands:
如果您有 1 个以上的命令需要不同的 shell,您应该阅读https://docs.docker.com/engine/reference/builder/#shell并通过将其放在 RUN 命令之前来更改默认 shell:
SHELL ["/bin/bash", "-c"]
Finally, if you have placed anything in the root user's .bashrc
file that you need, you can add the -l
flag to the SHELL
or RUN
command to make it a login shell and ensure that it gets sourced.
最后,如果您在 root 用户的.bashrc
文件中放置了您需要的任何内容,您可以将-l
标志添加到SHELL
orRUN
命令以使其成为登录 shell 并确保它被获取。
Note: I have intentionally ignored the fact that it is pointless to source a script as the only command in a RUN.
注意:我故意忽略了这样一个事实,即在 RUN 中将脚本作为唯一的命令是毫无意义的。
回答by Gianluca Casati
According to Docker documentation
根据 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"]
要使用除“/bin/sh”之外的其他 shell,请使用传入所需 shell 的 exec 形式。例如,
RUN ["/bin/bash", "-c", "echo hello"]
回答by Mohan
If you have SHELL
available you should go with this answer-- don'tuse the accepted one, which forces you to put the rest of the dockerfile in one command per this comment.
如果您有SHELL
可用的你应该去这个答案-不使用公认的一个,你每把其势力dockerfile的其余部分在一个命令此评论。
If you are using an old Docker version and don't have access to SHELL
, this will work so long as you don't need anything from .bashrc
(which is a rare case in Dockerfiles):
如果您使用的是旧的 Docker 版本并且无法访问SHELL
,那么只要您不需要任何来自的东西.bashrc
(这在 Dockerfiles 中很少见),这将起作用:
ENTRYPOINT ["bash", "--rcfile", "/usr/local/bin/virtualenvwrapper.sh", "-ci"]
Note the -i
is needed to make bash read the rcfile at all.
请注意,-i
需要让 bash 完全读取 rcfile。