在Dockerfile中激活Python虚拟环境

95

我有一个Dockerfile,在其中尝试激活Python虚拟环境,以便它可以在此环境中安装所有依赖项。然而,所有的东西仍然被全局安装了。我尝试了不同的方法,但都没有成功。我也没有收到任何错误提示。

问题在哪里?

1. ENV PATH $PATH:env/bin

2. ENV PATH $PATH:env/bin/activate

3. RUN . env/bin/activate

我还按照Google Cloud上Python运行时图像的Dockerfile配置示例进行了操作,这基本上是上面的相同内容。

设置这些环境变量就像运行source /env/bin/activate一样。

ENV VIRTUAL_ENV /env

ENV PATH /env/bin:$PATH

此外,ENV VIRTUAL_ENV /env的意思是什么,如何使用?


你试过执行 source ../bin/activate 命令了吗? - Vikas Periyadath
你是否正在同一Docker容器中运行多个Python应用程序? - Marcus Lind
1
在Dockerfile中使用virtualenv可能不是最佳实践,因为理想情况下,您应该只使用每个容器一个应用程序的做法进行全局安装。然而,我很高兴遇到了这个问题,因为我有一个单元测试用例需要在Dockerfile中使用virtualenv。这似乎很奇怪,但测试的一部分是针对virtualenv集成的。感谢您提出这个问题。 - Michael Mulich
回复:“所有东西仍然全局安装”。大多数情况下,当我看到这种情况发生时,是因为有人在使用全局的 pip。在您的 Docker 镜像中构建一个 venv,然后使用与目标虚拟环境相对应的 pip 将软件包安装到该虚拟环境中。如果您调用 /path/to/venv/bin/pip(请注意完整的 venv 路径),您很可能会成功。 - Mike Pennington
7个回答

98

在 Docker 容器中,您不需要使用 virtualenv。

virtualenv 用于依赖项隔离。您希望防止任何安装的依赖项或包泄漏到应用程序之间。而 Docker 则通过将您的依赖关系隔离在容器内,并防止容器和应用程序之间发生泄漏来实现同样的目的。

因此,在 Docker 容器中使用 virtualenv 没有意义,除非您在同一容器中运行多个应用程序。如果是这种情况,我认为您正在做错误的事情,解决方案是更好地架构您的应用程序并将它们拆分成多个容器。


编辑于2022年:鉴于这个答案获得了很多浏览量,我认为现在是时候补充一下,四年后我意识到在Docker镜像中实际上存在着有效的虚拟环境用法,特别是在进行多阶段构建时:

FROM python:3.9-slim as compiler
ENV PYTHONUNBUFFERED 1

WORKDIR /app/

RUN python -m venv /opt/venv
# Enable venv
ENV PATH="/opt/venv/bin:$PATH"

COPY ./requirements.txt /app/requirements.txt
RUN pip install -Ur requirements.txt

FROM python:3.9-slim as runner
WORKDIR /app/
COPY --from=compiler /opt/venv /opt/venv

# Enable venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . /app/
CMD ["python", "app.py", ]

在上面的 Dockerfile 示例中,我们创建了一个/opt/venv虚拟环境,并使用ENV语句激活它,然后将所有依赖项安装到此/opt/venv中,然后可以将此文件夹简单地复制到我们构建的runner阶段中。这可以帮助减小Docker镜像的大小。

29
重点是为了节省空间。您可以直接复制virtualenv目录,而无需在目标镜像中安装python3-virtualenv。这样可以避免安装整个工具链(gcc和其他依赖),从而节省几百兆字节的空间。 - Frederick Nord
8
许多Python包仅支持在虚拟环境中安装,此时能够在docker容器内激活venv会很有用。 - Daniel Shapero
1
@MarcusLind 我认为这个问题是关于将Python项目的内容打包到Docker中,而不需要在Docker内部拥有构建环境。Virtualenv在此处用于将所有依赖项打包到子目录中,以便您可以将它们复制到WORKDIR中。但是,这将失败,因为它无法处理必须针对其Docker镜像使用的任何基本操作系统构建的二进制依赖项。相反,解决方案是创建一个Docker镜像来构建依赖项,然后在多阶段构建中将它们复制到目标镜像中。 - nurettin
28
下投票关闭话题。如果作者关注虚拟环境与Docker一起使用的特定问题,则意味着他实际上需要在Docker中使用虚拟环境。 - Gill Bates
2
@GillBates 这只是一种假设(在SO上永久的辩论)。显然,如果有人问如何将糖放入他们的油箱中,我们不会都说,看,他们真的想知道如何将糖放入他们的油箱中。作者对于docker/venv的无知是未知的,因此很难确定他们真正想要什么。话虽如此,我不认为在容器中使用venv没有意义。我的情况是:基础镜像包与python安装存在冲突。 - jpmorris
显示剩余11条评论

73

在容器内使用虚拟环境是完全合理的原因存在。

您不一定需要激活虚拟环境来安装软件或使用它。相反,尝试直接从虚拟环境的bin目录调用可执行文件:

FROM python:2.7

RUN virtualenv /ve
RUN /ve/bin/pip install somepackage

CMD ["/ve/bin/python", "yourcode.py"]

你也可以设置PATH环境变量,这样所有后续的Python命令都会使用虚拟环境中的二进制文件。如https://pythonspeed.com/articles/activate-virtualenv-dockerfile/所述。

FROM python:2.7

RUN virtualenv /ve
ENV PATH="/ve/bin:$PATH"
RUN pip install somepackage

CMD ["python", "yourcode.py"]

3
如果yourcode.py创建了一个子进程,这种方法可能不起作用。你还需要按照monitorius的回答所述调整$PATH。 - wotanii
如果你的代码创建了一个调用python的子进程,可以使用sys.executable来获取虚拟环境解释器的路径。例如:subprocess.run([sys.executable, '-m', 'foo']),这通常对于许多其他场景也是一个好主意。 - sytech

35

设置这些变量

ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH

并不完全等同于仅仅运行

RUN . env/bin/activate

因为在 Dockerfile 中单个 RUN 内的激活不会影响该 RUN 下面的任何行。但是通过 ENV 设置环境变量将为所有 RUN 命令激活你的虚拟环境。

看这个例子:

RUN virtualenv env                       # setup env
RUN which python                         # -> /usr/bin/python
RUN . /env/bin/activate && which python  # -> /env/bin/python
RUN which python                         # -> /usr/bin/python

所以,如果您确实需要在整个Dockerfile中激活virtualenv,您需要像这样做:

RUN virtualenv env
ENV VIRTUAL_ENV /env                     # activating environment
ENV PATH /env/bin:$PATH                  # activating environment
RUN which python                         # -> /env/bin/python

1
另一个非常流行的选择是将bash脚本作为入口点运行,并让它完成其余的繁重工作。 - Arseniy
3
入口点在运行时执行,此时镜像已经构建并部署。如果你想要在运行时将你的包安装到虚拟环境中,而不是在镜像构建时安装,那么这应该是一个非常特殊的情况。 - monitorius

18

虽然我赞同Marcus的观点,这不是使用Docker的方法,但你可以按照自己的意愿去做。

直接使用Docker的RUN命令将无法在虚拟环境中执行您的指令,因此请使用/bin/bash将指令压缩为一行。下面的Dockerfile对我有效:

FROM python:2.7

RUN virtualenv virtual
RUN /bin/bash -c "source /virtual/bin/activate && pip install pyserial && deactivate"
...

这应该只会在虚拟环境中安装pyserial模块。


1
感谢提供的解决方案,尽管它对我没有用。现在,依赖项(django)已安装,但我无法找到它,因为python 2/3无法在虚拟环境内外导入它。我没有一个复杂的应用程序,因此我现在会坚持Docker的主要目的,尽管仍有一些线程解释了为什么在Docker容器中创建venv仍然是一个好的做法。示例 - igsm
希望你已经解决了问题。然而这很奇怪,你怎么检查安装位置的呢? - pinty
“&& deactivate” 在结尾真的需要吗?因为 Docker 会在新的 shell 中启动后续的 RUN 步骤,对吧? - rwitzel
没错,我只是为了保持干净,以防激活对文件系统产生任何影响,这些影响将保留在生成的Docker镜像中。它很可能是可有可无的。 - pinty
@pinty 你有关于你的回答是否有任何更新吗?因为PEP-668将于2023年发布,现在系统基本上会阻止非venv环境下的pip install - undefined

0

对我来说唯一有效的解决方案是这个

CMD ["/bin/bash", "-c", "source <your-env>/bin/activate && cd src && python main.py"]


-3
如果你正在使用Python 3.x:
RUN pip install virtualenv
RUN virtualenv -p python3.5 virtual
RUN /bin/bash -c "source /virtual/bin/activate"

如果您正在使用Python 2.x:
RUN pip install virtualenv
RUN virtualenv virtual
RUN /bin/bash -c "source /virtual/bin/activate"

-6
考虑迁移到 pipenv - 这是一个可以自动化 virtualenv 和 pip 交互的工具。它被 PyPA 推荐使用。
通过 pipenv 在 Docker 镜像中复制环境非常简单:
FROM python:3.7

RUN pip install pipenv

COPY src/Pipfile* ./

RUN pipenv install --deploy

...

1
不好意思,如果这是一个愚蠢的问题,但当使用实际镜像时,我如何使用由pipenv安装的依赖项?我的理解是,pipenv会安装到一个带有随机名称的虚拟环境中。因此,如果我拉取此镜像、克隆我的存储库并尝试运行 pipenv run pytest,那么它将无法从我的文件夹中访问已安装的要求。谢谢。 - RayB
1
这是个好问题!我个人会在我的回答中添加--system参数到RUN。然后你就可以直接调用pytest了。但是这有一些注意事项,关于特定操作系统的系统Python网站包内容可能不同。因此,这种方法不太适合企业级应用。但对于开发而言是可用的。对于企业级解决方案,你需要设置或捕获虚拟环境名称,依我之见。 - Sergey Nevmerzhitsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接