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

时间:2013-12-17 13:29:35

标签: bash shell docker

我有一个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

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

[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的默认基本映像)。

18 个答案:

答案 0 :(得分:259)

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

答案 1 :(得分:128)

原始答案

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

这适用于每个Ubuntu docker基础映像。我通常为我写的每个Dockerfile添加这一行。

由有关旁观者编辑

如果您希望在整个Dockerfile中使用“bash而不是sh”,则不会更改并且可能会损坏 *容器内的操作系统,您只需tell Docker your intention。这是这样做的:

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

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

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

答案 2 :(得分:43)

我有同样的问题,为了在virtualenv中执行pip安装,我不得不使用这个命令:

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"

我希望它有所帮助。

答案 3 :(得分:39)

最简单的方法是使用点运算符代替source,它是bash source命令的等价物:

而不是:

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

使用:

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

答案 4 :(得分:37)

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

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

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

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

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

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

附加说明:您还可以添加--login选项以启动登录shell。这意味着例如~/.bachrc将被读取,您无需在命令

之前明确地获取它

答案 5 :(得分:20)

在此页面上的答案的基础上,我想补充一点,你必须知道每个RUN语句独立于/bin/sh -c的其他语句运行,因此不会获得通常在登录时获取的任何环境变量壳。

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

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 startup files上的手册有助于了解何时采购。

答案 6 :(得分:17)

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

简答:

一般:

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

for 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上的默认shell是   [" / bin / sh"," -c"],在Windows上是[" cmd"," / S",&# 34 / C&#34]。贝壳   必须在Dockerfile中以JSON格式编写指令。

     

SHELL指令在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
     

SHELL指令可能会影响以下指令   当它们的shell形式在Dockerfile中使用时:RUN,CMD和   入口点。

     

以下示例是在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形式的指令需要额外的powershell -command   为命令添加前缀。

     

为了提高效率,可以采用两种机制中的一种。   一种是使用RUN命令的JSON形式,例如:

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

虽然JSON表单是明确的,但不使用不必要的表单   cmd.exe,它确实需要通过双引号和更多的详细程度   逃跑。替代机制是使用SHELL指令和   shell表单,为Windows用户提供更自然的语法,   特别是与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'
     

导致:

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指令也可用于修改a的方式   shell操作。例如,使用SHELL cmd / S / C / V:ON | OFF   Windows,延迟环境变量扩展语义可以   修改。

     

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

     

在Docker 1.12中添加了SHELL功能。

答案 7 :(得分:11)

根据https://docs.docker.com/engine/reference/builder/#runRUN的默认[Linux] shell为/bin/sh -c。你似乎期待bashisms,所以你应该使用" exec形式" RUN指定你的shell。

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

否则,使用" shell表单" RUN并指定不同的shell会导致嵌套shell。

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

最后,如果您在root用户的.bashrc文件中放置了所需内容,则可以将-l标记添加到SHELL或{{1}命令使其成为登录shell并确保它获取源。

注意:我故意忽略了这样一个事实:将脚本作为RUN中唯一的命令来源是没有意义的。

答案 8 :(得分:8)

根据Docker文档

  

要使用除'/ bin / sh'之外的其他shell,请使用传入所需shell的exec表单。例如,

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

请参阅https://docs.docker.com/engine/reference/builder/#run

答案 9 :(得分:3)

我在Dockerfile中运行source时遇到了问题

这对于构建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

答案 10 :(得分:2)

您可能希望运行bash -v以查看来源。

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

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

答案 11 :(得分:2)

这可能会发生,因为source是bash的内置内容,而不是文件系统上某处的二进制文件。你打算使用脚本来改变容器吗?

答案 12 :(得分:0)

如果您只是尝试使用pip在virtualenv中安装某些内容,则可以修改PATH环境以首先查看virtualenv的bin文件夹

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

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

答案 13 :(得分:0)

我最终把我的env内容放在.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

答案 14 :(得分:0)

如果您有SHELL可用,则应使用this answer-不要使用已接受的一个,这将迫使您将其余的dockerfile放入一个this comment中的命令。

如果您使用的是较旧的Docker版本,但无权访问SHELL,那么只要您不需要.bashrc中的任何内容,这便会起作用(在Dockerfiles中很少见):

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

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

答案 15 :(得分:0)

这是一个示例Dockerfile,它利用几种巧妙的技术为您提供了每个$audit->getMetadata()节的完整conda环境。您可以使用类似的方法在脚本文件中执行任意准备。

注意:关于登录/交互式与非登录/非交互式外壳,信号RUN,处理多个arg的方式,引用how CMD and ENTRYPOINT interact和一百万,有很多细微差别其他事物,因此,如果在使用这些事物进行黑客入侵时发现东西横摆,不要气be。我花了许多令人沮丧的时间来研究各种文学作品,但我仍然不太了解所有点击的方式。

exec

答案 16 :(得分:0)

对于使用Django Web网络框架开发的应用程序,我正在处理类似的情况,而这些步骤对我来说是完美的:

  • 我的 Dockerfile
  • 的内容
[mlazo@srvjenkins project_textile]$ cat docker/Dockerfile.debug 
FROM malazo/project_textile_ubuntu:latest 

ENV PROJECT_DIR=/proyectos/project_textile PROJECT_NAME=project_textile WRAPPER_PATH=/usr/share/virtualenvwrapper/virtualenvwrapper.sh

COPY . ${PROJECT_DIR}/
WORKDIR ${PROJECT_DIR}

RUN echo "source ${WRAPPER_PATH}" > ~/.bashrc
SHELL ["/bin/bash","-c","-l"]
RUN     mkvirtualenv -p $(which python3) ${PROJECT_NAME} && \
        workon ${PROJECT_NAME} && \
        pip3 install -r requirements.txt 

EXPOSE 8000

ENTRYPOINT ["tests/container_entrypoint.sh"]
CMD ["public/manage.py","runserver","0:8000"]

  • ENTRYPOINT 文件“ tests / container_entrypoint.sh ”中的内容:
[mlazo@srvjenkins project_textile]$ cat tests/container_entrypoint.sh
#!/bin/bash
# *-* encoding : UTF-8 *-*
sh tests/deliver_env.sh
source ~/.virtualenvs/project_textile/bin/activate 
exec python "$@"

  • 最后,我部署容器的方式是:
[mlazo@srvjenkins project_textile]$ cat ./tests/container_deployment.sh 
#!/bin/bash

CONT_NAME="cont_app_server"
IMG_NAME="malazo/project_textile_app"
[ $(docker ps -a |grep -i ${CONT_NAME} |wc -l) -gt 0 ] && docker rm -f ${CONT_NAME} 
docker run --name ${CONT_NAME} -p 8000:8000 -e DEBUG=${DEBUG} -e MYSQL_USER=${MYSQL_USER} -e MYSQL_PASSWORD=${MYSQL_PASSWORD} -e MYSQL_HOST=${MYSQL_HOST} -e MYSQL_DATABASE=${MYSQL_DATABASE} -e MYSQL_PORT=${MYSQL_PORT}  -d ${IMG_NAME}

我真的希望这对其他人有帮助。

问候,

答案 17 :(得分:0)

我遇到了同样的问题。如果您还使用 Python 基础映像,则可以将 shell 脚本中的 shebang 行更改为 #!/bin/bash。 例如,参见 Manuel Lazo 中的 container_entrypoint.sh