Docker 与 Python:开发环境搭建指南 – wiki基地


Docker 与 Python:开发环境搭建指南

引言:告别“环境地狱”

在 Python 开发的世界里,环境管理一直是一个令人头疼的问题。不同的项目依赖不同的 Python 版本、不同版本的库。有时候,一个项目需要 Django 2.x,另一个需要 Django 3.x;一个需要特定版本的 TensorFlow,另一个则不能兼容。传统的解决方案如 virtualenvconda 确实能隔离不同项目的依赖库,但它们仍然依赖于系统全局安装的 Python 版本,并且难以解决更深层次的环境一致性问题,比如系统级的依赖库差异、操作系统差异等。

“这在我的机器上运行得好好的!”(”It works on my machine!”)这句充满无奈的话,正是环境不一致性的真实写照。开发者花费大量时间解决环境冲突、在新机器上重现旧环境、排查因环境差异导致的奇怪 bug。

有没有一种方法,能够彻底隔离开发环境,让它像一个独立的、可移植的“黑盒”,无论在哪个系统上都能一致地运行?答案是肯定的,那就是 Docker。

Docker 作为一种容器化技术,提供了一种轻量级、可移植、自给自足的环境打包方案。它将应用及其所有依赖(代码、运行时、系统工具、库等)打包到一个称为“镜像”(Image)的标准单元中。基于镜像运行的实例称为“容器”(Container)。容器之间相互隔离,互不影响,且与底层基础设施解耦。

将 Docker 应用于 Python 开发环境,意味着我们可以:

  1. 彻底隔离环境: 每个项目都可以拥有自己独立的 Python 版本、库版本以及系统级依赖,相互之间完全隔离。
  2. 环境一致性: 通过 Dockerfile 定义环境,确保团队成员、CI/CD 系统乃至生产环境使用完全相同的环境。
  3. 快速搭建: 新成员加入团队或在新机器上开始工作时,无需繁琐的安装步骤,只需构建或拉取 Docker 镜像即可。
  4. 简化依赖管理: 将所有依赖打包进容器,避免了系统层面复杂的依赖冲突。
  5. 提高开发效率: 减少环境问题带来的阻碍,让开发者更专注于代码本身。

本文将详细介绍如何利用 Docker 搭建高效、可移植的 Python 开发环境,从基础概念到实际操作,再到结合 Docker Compose 管理多个服务,最后探讨与主流 IDE 的集成。

Docker 核心概念速览

在深入实践之前,先简要回顾几个核心 Docker 概念:

  • 镜像 (Image): 一个只读的模板,包含了运行容器所需的一切,包括代码、运行时、库、环境变量和配置文件等。可以类比为虚拟机中的“镜像文件”。镜像是通过 Dockerfile 构建的。
  • 容器 (Container): 镜像的可运行实例。容器提供了沙盒环境,是应用运行的进程。容器可以被创建、启动、停止、删除。容器是相互隔离的。
  • Dockerfile: 一个文本文件,包含了一系列指令,用于自动化构建 Docker 镜像。每一条指令都在镜像中创建一个层(layer)。
  • 层 (Layer): Docker 镜像由一系列只读的层组成。每条 Dockerfile 指令都会创建一个新层。层的好处在于可以被缓存和重用,节省存储空间和构建时间。
  • 卷 (Volume): 用于持久化存储数据或在容器与宿主机之间共享数据。容器在停止或删除后,其内部文件系统的改动会丢失,但卷中的数据会保留。这对于开发中的代码同步和数据库数据持久化至关重要。
  • Docker Hub/Registry: 存放 Docker 镜像的仓库。Docker Hub 是官方公共仓库,你也可以使用私有仓库。

安装 Docker

开始之前,你需要在你的操作系统上安装 Docker。访问 Docker 官方网站 下载并安装适用于你操作系统的 Docker Desktop(Windows 或 macOS)或 Docker Engine(Linux)。安装过程通常比较简单,按照官方文档指引即可。安装完成后,在终端运行 docker --versiondocker compose version(或 docker-compose --version,取决于你的安装方式)来验证安装是否成功。

构建基础 Python 开发环境

我们将从最简单的 Python 应用开始,逐步构建和完善 Docker 环境。

1. 项目结构

首先,创建一个简单的项目目录:

bash
mkdir my-python-app
cd my-python-app

在这个目录中,我们需要:
* app.py: 我们的 Python 代码。
* requirements.txt: 项目依赖库列表。
* Dockerfile: 用于构建 Docker 镜像的指令文件。

2. 编写 Python 代码 (app.py)

创建一个简单的 app.py 文件,比如:

“`python

app.py

import sys
import time

print(“Hello from Docker container!”)
print(f”Python version: {sys.version}”)

print(“Sleeping for 5 seconds…”)
time.sleep(5)
print(“Done sleeping. Exiting.”)
“`

这个脚本只是打印一些信息并暂停几秒钟。

3. 定义依赖 (requirements.txt)

创建一个 requirements.txt 文件,列出项目依赖。对于这个简单的例子,我们没有外部依赖,但为了演示,我们可以加入一个常用的库:

“`txt

requirements.txt

requests==2.26.0
“`

在实际项目中,你会在这里列出所有的第三方库及其版本。强烈建议固定库的版本,以确保环境的可重现性。

4. 编写 Dockerfile

Dockerfile 是定义 Docker 镜像构建过程的核心文件。在项目根目录创建 Dockerfile 文件:

“`dockerfile

Dockerfile

1. 选择一个基础镜像

我们使用官方的 Python 镜像。选择带版本号和 ‘slim’ 或 ‘alpine’ 后缀的镜像通常更好,

因为它们更小。’slim’ 基于 Debian 发行版,’alpine’ 基于 Alpine Linux。

这里使用 python:3.9-slim 作为示例

FROM python:3.9-slim

2. 设置工作目录

WORKDIR 指令为后续的 RUN, CMD, ENTRYPOINT, COPY, ADD 指令设置工作目录。

如果目录不存在,会自动创建。

WORKDIR /app

3. 将本地的 requirements.txt 文件复制到容器的工作目录 /app 中

COPY <源路径> <目标路径>

COPY requirements.txt .

4. 安装项目依赖

使用 RUN 指令在镜像构建过程中执行命令。

‘-r’ 指定从 requirements.txt 读取依赖。

‘–no-cache-dir’ 选项可以减少镜像大小。

‘pip install’ 命令通常需要网络连接。

RUN pip install –no-cache-dir -r requirements.txt

5. 将本地项目代码复制到容器的工作目录 /app 中

将当前目录(包含 app.py)下的所有内容复制到容器的 /app 目录

COPY . .

6. 定义容器启动时执行的命令

CMD 指令指定容器启动时默认执行的命令。

ENTRYPOINT 也可以用于指定启动命令,但 CMD 更常用于指定可变参数或默认命令。

这里使用 exec 形式,推荐在 CMD/ENTRYPOINT 中使用,可以避免一些信号处理问题。

CMD [“python”, “app.py”]

7. (可选)声明暴露的端口

如果你的应用是一个 Web 应用(如 Flask/Django),需要暴露端口。

EXPOSE 指令只是一个文档说明,真正映射端口需要在 docker run 或 docker-compose 中进行。

EXPOSE 8000

“`

解释一下 Dockerfile 中的每一步:

  • FROM python:3.9-slim: 基于官方的 Python 3.9 slim 镜像构建。slim 版本比完整版小很多,因为它不包含许多开发或调试工具,但包含了运行 Python 应用所需的最小集。
  • WORKDIR /app: 设置容器内部的工作目录为 /app。之后的所有命令都将在这个目录下执行。
  • COPY requirements.txt .: 将宿主机当前目录下的 requirements.txt 文件复制到容器工作目录 (/app) 下。
  • RUN pip install --no-cache-dir -r requirements.txt: 在容器中运行 pip install 命令安装依赖。这一步非常重要,它将所有 Python 库安装到容器环境里。
  • COPY . .: 将宿主机当前目录下的所有文件(包括 app.py)复制到容器工作目录 (/app) 下。注意这一步放在安装依赖之后,利用了 Docker 层缓存的特性:如果 requirements.txt 不变,即使 app.py 改变,Docker 也会使用之前安装依赖的缓存层,加快构建速度。
  • CMD ["python", "app.py"]: 指定容器启动时执行的命令。这里是运行 app.py 脚本。

5. 构建 Docker 镜像

Dockerfile 所在的目录(即 my-python-app 目录)打开终端,执行构建命令:

bash
docker build -t my-python-app:latest .

  • docker build: Docker 构建命令。
  • -t my-python-app:latest: 为构建的镜像打标签 (tag)。my-python-app 是镜像名称,:latest 是标签,通常用于表示最新版本。你可以用任何有意义的名称和标签。
  • .: 指定 Dockerfile 所在的路径。. 表示当前目录。

Docker 会读取 Dockerfile 并按顺序执行其中的指令。你会看到每一步构建的过程,以及 Docker 如何利用层缓存。构建成功后,你可以运行 docker images 命令查看你的新镜像。

6. 运行 Docker 容器

现在我们可以基于刚刚构建的镜像运行一个容器:

bash
docker run --rm my-python-app:latest

  • docker run: Docker 运行容器命令。
  • --rm: 容器停止后自动删除容器。这在开发和测试中非常有用,避免产生大量停止的容器。
  • my-python-app:latest: 指定要运行的镜像名称和标签。

你应该会看到容器输出 app.py 脚本的打印信息:

Hello from Docker container!
Python version: 3.9.18 (main, Nov 2 2023, 00:43:14)
[GCC 12.2.0]
Sleeping for 5 seconds...
Done sleeping. Exiting.

容器执行完脚本后会自动停止,并因为 --rm 标志而被删除。

至此,你已经成功地在 Docker 容器中运行了一个 Python 应用。然而,这仅仅是第一步。当前的 setup 适用于一次性任务或最终部署,但对于开发来说,它有两个主要问题:

  1. 代码修改不方便: 每次修改 app.py 都需要重新构建镜像,这非常低效。
  2. 调试困难: 难以直接在容器中进行交互式调试。

接下来,我们将解决这些问题,使其更适合开发工作流。

改进开发工作流:使用卷 (Volumes)

为了方便修改代码并立即在容器中看到效果,我们需要将本地的项目代码目录“映射”到容器内的代码目录。这就是卷(Volume)的作用,特别是绑定挂载 (Bind Mount)

绑定挂载允许我们将宿主机文件系统上的一个目录或文件直接挂载到容器内的指定路径。这样,在宿主机上对文件进行的修改会立即反映到容器中,反之亦然。

修改运行容器的命令,添加 -v 参数进行绑定挂载:

“`bash

docker run -v <宿主机路径>:<容器路径> <镜像名称>

docker run –rm -v $(pwd):/app my-python-app:latest
“`

  • -v $(pwd):/app: 将当前的宿主机目录 ($(pwd) 在 Linux/macOS 中表示当前路径,在 Windows PowerShell 中是 ${PWD},在 Windows CMD 中是 %cd%) 绑定挂载到容器内的 /app 目录。注意,宿主机路径需要是绝对路径,$(pwd) 可以方便地获取当前绝对路径。

现在,再次运行上面的命令,然后尝试在运行容器的同时修改 app.py 文件。你会发现容器执行的是修改后的代码(如果你的应用逻辑会反复执行的话)。对于我们简单的 app.py 脚本,修改后需要重新运行容器才能看到效果,因为脚本执行完毕就退出了。

对于 Web 应用 (如 Flask/Django),使用绑定挂载与开发服务器(如 flask run --reloadmanage.py runserver)的热重载功能结合得非常好。你修改代码并保存,开发服务器会自动检测到文件变化并重启或重新加载,容器内的应用也随之更新,无需手动重启容器或重新构建镜像。

卷的另一个用途:依赖缓存

除了代码,我们还可以将 pip 安装的 Python 库缓存到宿主机或一个单独的卷中,以加快后续构建或容器启动的速度。

Dockerfile 中,我们已经通过 RUN pip install -r requirements.txt 将依赖安装到镜像中了。这意味着每次构建新镜像时,如果 requirements.txt 改变,pip install 步骤会重新执行。

另一种方式是,在开发时,我们可以不将 requirements.txt 包含在镜像中,而是在容器启动后,通过绑定挂载的 requirements.txt 文件,在容器内执行安装命令。但这通常不如在构建镜像时安装依赖来得方便和标准。

更常见的是利用 Docker 的层缓存或者,在某些特定场景下(比如频繁切换依赖分支进行测试),考虑将 pip 缓存目录或 site-packages 目录也挂载为卷。然而,对于大多数 Python 开发场景,依赖的稳定性比代码低,将依赖安装在镜像层中,利用 Docker 的构建缓存(只要 requirements.txt 不变,安装步骤就不会重新执行),通常是更简洁且效率不错的做法。

结合 Web 框架 (Flask/Django)

大多数 Python 开发都涉及到 Web 框架。让我们看看如何用 Docker 运行一个简单的 Flask 应用。

1. 项目结构

my-flask-app/
├── app/
│ ├── __init__.py
│ └── routes.py
├── requirements.txt
└── Dockerfile

2. 编写 Flask 应用 (app/ 目录)

app/__init__.py:

“`python

app/init.py

from flask import Flask

def create_app():
app = Flask(name)

from . import routes
app.register_blueprint(routes.bp)

return app

if name == ‘main‘:
app = create_app()
app.run(host=’0.0.0.0’, port=5000, debug=True) # debug=True enables reloader
“`

app/routes.py:

“`python

app/routes.py

from flask import Blueprint

bp = Blueprint(‘main’, name)

@bp.route(‘/’)
def index():
return “Hello, Flask in Docker!”

@bp.route(‘/greet/‘)
def greet(name):
return f”Hello, {name}!”
“`

3. 定义依赖 (requirements.txt)

txt
Flask==2.2.2 # 或者你需要的版本
gunicorn==20.1.0 # 通常用于生产环境,开发时 Flask 内置服务器即可

4. 编写 Dockerfile

“`dockerfile

Dockerfile

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install –no-cache-dir -r requirements.txt

Copy the entire application code

COPY app /app/app

Expose the port the Flask app will run on

This is just documentation, actual port mapping is done with docker run -p

EXPOSE 5000

Command to run the Flask development server

Use exec form

Running with debug=True and host=’0.0.0.0′ is crucial for development

CMD [“python”, “-m”, “app”]
“`

5. 构建镜像

bash
docker build -t my-flask-app:latest .

6. 运行容器 (开发模式)

使用绑定挂载来同步代码,并进行端口映射,以便从宿主机访问 Flask 应用。

“`bash

docker run -p <宿主机端口>:<容器端口> -v <宿主机代码路径>:<容器代码路径> …

docker run –rm -p 5000:5000 -v $(pwd)/app:/app/app my-flask-app:latest
“`

  • -p 5000:5000: 将宿主机的 5000 端口映射到容器的 5000 端口。这样你就可以通过访问 http://localhost:5000 (或 http://127.0.0.1:5000) 从宿主机浏览器访问容器内运行的 Flask 应用。
  • -v $(pwd)/app:/app/app: 将宿主机的 ./app 目录绑定挂载到容器内的 /app/app 目录。这样你在本地修改 app 目录下的 Python 文件, Flask 开发服务器会自动重载。

现在,你应该可以在浏览器中访问 http://localhost:5000 看到 “Hello, Flask in Docker!” 了。修改 app/routes.py 中的文本,保存,无需重启容器,刷新浏览器即可看到变化。

对于 Django 应用,流程类似:复制 Django 项目文件,安装依赖,暴露 Django 开发服务器端口(默认为 8000),使用绑定挂载。CMD 命令会是运行 python manage.py runserver 0.0.0.0:8000

管理多个服务:Docker Compose

现实世界的应用往往不止一个服务。一个典型的 Web 应用可能包括:

  • Python Web 服务 (Flask/Django)
  • 数据库 (PostgreSQL, MySQL)
  • 缓存 (Redis)
  • 消息队列 (RabbitMQ, Celery)

手动使用 docker run 命令启动、连接和管理这些相互依赖的容器会非常复杂。Docker Compose 就是为了解决这个问题而生的。

Docker Compose 允许你使用一个 YAML 文件 (docker-compose.yml) 来定义和管理多个 Docker 容器应用。你可以定义各个服务、它们使用的镜像、端口映射、卷、依赖关系、环境变量等等。

1. 安装 Docker Compose

Docker Desktop 通常已经包含了 Docker Compose V2 版本(使用 docker compose 命令)。如果使用旧版本或 Linux 系统,可能需要单独安装(使用 docker-compose 命令)。请参考 Docker 官方文档。

2. 项目结构(带数据库)

假设我们的 Flask 应用需要连接到一个 PostgreSQL 数据库。项目结构可能如下:

my-flask-app-compose/
├── app/
│ ├── __init__.py
│ └── routes.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml

app/ 目录、requirements.txtDockerfile 与前面的 Flask 示例基本相同,但我们将数据库连接信息从硬编码改为使用环境变量。

修改 app/__init__.py 以读取数据库配置(示例,实际需要安装数据库驱动如 psycopg2-binary 并实现数据库连接逻辑):

“`python

app/init.py

import os
from flask import Flask

Example: Get DB config from environment variables

DB_HOST = os.environ.get(“DB_HOST”, “localhost”)
DB_NAME = os.environ.get(“DB_NAME”, “mydb”)
DB_USER = os.environ.get(“DB_USER”, “myuser”)
DB_PASSWORD = os.environ.get(“DB_PASSWORD”, “mypassword”)

print(f”Connecting to database: postgresql://{DB_USER}:**@{DB_HOST}:5432/{DB_NAME}”)

In a real app, you would establish a database connection here…

def create_app():
app = Flask(name)

from . import routes
app.register_blueprint(routes.bp)

return app

if name == ‘main‘:
app = create_app()
app.run(host=’0.0.0.0’, port=5000, debug=True)
“`

修改 requirements.txt

txt
Flask==2.2.2
psycopg2-binary # Add database driver

Dockerfile 保持不变。

3. 编写 docker-compose.yml

在项目根目录创建 docker-compose.yml 文件:

“`yaml

docker-compose.yml

version: ‘3.8’ # 推荐使用较新的版本

services:
# 定义 Python Web 服务
app:
build: . # 指定构建上下文为当前目录,Docker Compose 会在这里查找 Dockerfile
ports:
– “5000:5000” # 将宿主机 5000 端口映射到容器 5000 端口
volumes:
– ./app:/app/app # 绑定挂载代码目录,方便开发
# 如果需要在容器内安装额外依赖(不推荐用于 dev 阶段)或缓存 pip 包,可以考虑其他卷
# – venv_cache:/root/.cache/pip # 示例:缓存 pip
environment: # 为容器设置环境变量
DB_HOST: db # 数据库服务名称,Docker Compose 会自动创建网络并解析服务名
DB_NAME: mydb
DB_USER: myuser
DB_PASSWORD: mypassword
depends_on: # 声明服务依赖关系
– db # app 服务依赖于 db 服务,Compose 会确保 db 先启动
# command: python -m app # 覆盖 Dockerfile 中的 CMD 命令,如果需要

# 定义 PostgreSQL 数据库服务
db:
image: postgres:13 # 使用官方 PostgreSQL 镜像
environment: # 设置数据库的环境变量,用于初始化数据库
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
# 绑定挂载或命名卷用于数据持久化
# 推荐使用命名卷,因为它不依赖宿主机特定目录结构
– db_data:/var/lib/postgresql/data # 将容器内的数据目录挂载到命名卷 db_data
# ports:
# – “5432:5432” # 通常开发时不需要将数据库端口暴露给宿主机,除非你有宿主机上的工具需要连接

定义命名卷,用于持久化数据库数据

volumes:
db_data: # 命名卷会自动创建和管理
# venv_cache: # 示例:如果需要缓存 pip 包
“`

解释 docker-compose.yml 文件:

  • version: '3.8': 指定 Compose 文件格式的版本。
  • services:: 定义组成应用的各个服务。
    • app:: 定义我们的 Python Web 服务。
      • build: .: 告诉 Compose 在当前目录下查找 Dockerfile 并构建镜像。你也可以使用 image: existing-image 来直接使用已有的镜像。
      • ports: - "5000:5000": 将宿主机的 5000 端口映射到 app 容器的 5000 端口。
      • volumes: - ./app:/app/app: 将宿主机的 ./app 目录绑定挂载到容器的 /app/app 目录,实现代码同步。
      • environment:: 定义传递给容器的环境变量。DB_HOST: db 是关键,因为 Docker Compose 会为所有服务创建一个默认网络,服务之间可以通过服务名称相互访问。这里的 db 就是 PostgreSQL 容器的服务名称。
      • depends_on: - db: 声明 app 服务依赖于 db 服务。Compose 会在启动 app 之前启动 db
    • db:: 定义 PostgreSQL 数据库服务。
      • image: postgres:13: 使用官方的 postgres:13 镜像。
      • environment:: 设置 PostgreSQL 镜像用于初始化数据库的环境变量(数据库名、用户名、密码)。
      • volumes: - db_data:/var/lib/postgresql/data: 使用名为 db_data 的命名卷来持久化数据库的数据文件。即使 db 容器被删除,数据也会保留在命名卷中,下次启动时会重新挂载。
  • volumes:: 定义命名卷。这里定义了 db_data 卷。

4. 使用 Docker Compose 启动应用

docker-compose.yml 所在的目录打开终端,执行:

bash
docker compose up -d

  • docker compose up: 构建、创建并启动 docker-compose.yml 中定义的所有服务。
  • -d: 在后台模式 (detached mode) 运行容器,不占用当前终端。

Compose 会:
1. 如果 app 服务的镜像不存在或 Dockerfile 有变化,会构建新的镜像。
2. 创建 Docker 网络(默认名称为 my-flask-app-compose_default,基于目录名)。
3. 创建 db_data 命名卷(如果不存在)。
4. 启动 db 容器。
5. 启动 app 容器,并将其连接到同一个网络,同时注入环境变量。

你可以使用 docker compose ps 查看正在运行的服务容器。

现在,你可以通过访问 http://localhost:5000 访问你的 Flask 应用了。在 app/ 目录修改代码,保存,Flask 开发服务器会自动重载。

5. 查看日志和执行命令

  • 查看所有服务的日志:
    bash
    docker compose logs
  • 查看特定服务的日志:
    bash
    docker compose logs app
  • 在运行中的服务容器中执行命令(例如进入 shell 或运行迁移命令):
    bash
    docker compose exec app bash # 进入 app 容器的 bash shell
    # 或者
    docker compose exec app python manage.py migrate # 示例:在 Django 应用中运行迁移

6. 停止和删除应用

bash
docker compose down

此命令会停止并删除 docker-compose.yml 中定义的所有容器和网络。注意: 默认情况下,docker compose down 不会删除命名卷。这意味着你的数据库数据会保留下来。

如果要连同命名卷一起删除(慎用,数据会丢失):

bash
docker compose down --volumes

Docker Compose 极大地简化了多服务应用的开发和管理。你可以轻松定义复杂的依赖关系、网络和卷,并通过简单的命令启动、停止和管理整个应用栈。

高级主题与最佳实践

1. 选择合适的基础镜像

  • python:<version>: 完整版镜像,包含了许多系统库和工具,体积较大。
  • python:<version>-slim: 基于 Debian 发行版,移除了非必需的组件,体积显著减小,但仍然包含常见的系统库,兼容性较好。通常是 Python 开发的最佳选择。
  • python:<version>-alpine: 基于 Alpine Linux,体积非常小。但 Alpine 使用 musl libc 而非 glibc,这可能导致某些依赖(如 NumPy, Pandas 及其依赖的 C 库)编译或运行出现问题。如果你确信所有依赖都能在 Alpine 上良好运行,并且对镜像大小有极致要求,可以考虑。

总是指定具体的版本号(如 python:3.9-slim),避免使用 :latest 作为基础镜像,以确保构建的可重现性。

2. 利用构建缓存

Docker 构建过程是分层的。每一条指令都会创建一个新层。Docker 会缓存构建过程中已经执行过的层的输出。如果在构建时,某个指令及其之前的所有指令、以及该指令依赖的文件都没有改变,Docker 就会使用缓存,跳过该指令及其后续指令的执行,大大加快构建速度。

在 Dockerfile 中,将变化频率较低的指令放在前面,变化频率较高的指令放在后面。例如,先复制 requirements.txt 并安装依赖 (pip install),再复制应用代码。如果只修改了代码,而 requirements.txt 不变,Docker 在安装依赖步骤会直接使用缓存,只重新执行复制代码和 CMD/ENTRYPOINT 步骤。

“`dockerfile

Dockerfile

FROM python:3.9-slim

WORKDIR /app

先复制依赖文件(变化频率较低)

COPY requirements.txt .

再安装依赖(依赖文件不变则命中缓存)

RUN pip install –no-cache-dir -r requirements.txt

最后复制应用代码(变化频率较高)

COPY . .

CMD [“python”, “app.py”]
“`

3. 处理环境变量

  • Dockerfile 中的环境变量: 使用 ENV 指令设置构建时或容器运行时的环境变量。不建议在 ENV 中存放敏感信息。
  • docker run 中的环境变量: 使用 -e KEY=VALUE--env-file <file> 设置。
  • Docker Compose 中的环境变量:docker-compose.ymlenvironment 部分设置,或使用 .env 文件(Compose 会自动加载同目录下的 .env 文件)。推荐使用 .env 文件管理敏感信息,并将其添加到 .gitignore

4. 调试

在 Docker 容器中调试 Python 代码有几种方式:

  • 进入容器调试: 使用 docker exec -it <container_id_or_name> bash 进入容器,然后像在本地一样使用 pdb 或其他命令行调试工具。
  • 远程调试: 配置你的 IDE(如 VS Code, PyCharm)进行远程调试。这通常需要在容器内运行调试服务器(如 debugpy),然后在宿主机 IDE 中配置连接到容器的调试端口。IDE 的 Docker 集成通常能简化这一过程(见下一节)。

5. 忽略不需要的文件

在复制文件到镜像时,可以使用 .dockerignore 文件忽略不需要的文件和目录,比如 .git__pycache__.vscode、虚拟环境目录等。这可以减少构建上下文的大小,加快构建速度,并减小镜像体积。

创建 .dockerignore 文件:

gitignore
.git
.gitignore
__pycache__
*.pyc
.pytest_cache
.venv
venv
.vscode
node_modules # 如果前端代码也在同一个目录

6. 用户与权限

默认情况下,容器内的进程以 root 用户身份运行。出于安全考虑,尤其是在生产环境中,建议在 Dockerfile 中创建一个非 root 用户并切换到该用户运行应用:

“`dockerfile

… (之前的指令)

创建一个非 root 用户

RUN adduser –disabled-password –gecos ” appuser

将工作目录的所有权赋给新用户

RUN chown -R appuser:appuser /app

切换到新用户

USER appuser

… (CMD/ENTRYPOINT 指令)

CMD [“python”, “app.py”]
“`

在开发环境中,为了方便调试或安装临时工具,有时仍会使用 root 用户,但这需要权衡安全性与便利性。

与 IDE 集成

现代 IDE 大多提供了对 Docker 的良好支持,可以极大地提升开发体验。

1. VS Code

VS Code 的 Remote Development 扩展包(特别是 Remote – Containers 扩展)对 Docker 开发环境提供了优秀的支持。

  • 开发容器 (Development Containers): VS Code 可以直接在 Docker 容器中打开项目文件夹,并在容器内运行代码、调试、使用终端等。你需要一个 .devcontainer/devcontainer.json 文件来配置开发容器的构建和运行方式。这个文件可以基于 Dockerfiledocker-compose.yml。VS Code 会自动构建并启动容器,然后将开发工具(如 Python 插件)安装到容器内部,提供无缝的开发体验。你就像在本地开发一样,但所有操作都在隔离的容器环境中进行。

步骤概要:
1. 安装 Remote Development 扩展包。
2. 在项目根目录创建 .devcontainer/devcontainer.json 文件。
3. 配置 devcontainer.json,指定使用你的 Dockerfiledocker-compose.yml,设置卷、端口映射、安装 VS Code 插件等。
4. 在 VS Code 中,使用 “Remote-Containers: Open Folder in Container…” 命令打开项目。

2. PyCharm

PyCharm Professional 版本提供了对 Docker 和 Docker Compose 的原生支持,可以将 Docker 容器配置为项目的 Python 解释器。

步骤概要:
1. 打开 PyCharm 项目。
2. 进入 File -> Settings/Preferences -> Project: <Project Name> -> Python Interpreter
3. 点击右上角的齿轮图标,选择 Add Interpreter
4. 选择 DockerDocker Compose
* Docker: 选择 Docker 服务器,指定要使用的镜像或现有容器,配置路径映射(PyCharm 会自动映射项目根目录)。PyCharm 会在容器中执行 Python 命令。
* Docker Compose: 选择 Docker Compose 配置文件 (docker-compose.yml) 和要作为解释器的服务(如 app)。PyCharm 会使用 docker compose exec <service_name> python 命令在运行中的容器内执行 Python 脚本。
5. 配置完成后,PyCharm 会加载容器内的 Python 包列表,你可以像使用本地解释器一样运行、调试代码。

使用 IDE 的 Docker 集成,你可以直接在 IDE 中进行代码编辑、断点调试、运行测试等操作,而这些都在容器环境中完成,极大地提高了开发效率和便利性。

总结

通过本文的介绍,我们了解了为什么需要使用 Docker 来搭建 Python 开发环境,以及如何从零开始构建 Docker 镜像、运行容器。我们学习了如何利用卷实现代码的实时同步,提高了开发效率。接着,我们探讨了如何结合 Docker Compose 管理包含数据库等多个服务的复杂应用。最后,我们介绍了一些高级主题和最佳实践,并指导了如何将 Docker 环境与 VS Code 和 PyCharm 等主流 IDE 集成。

Docker 为 Python 开发者提供了一种强大且灵活的环境管理方案。它解决了传统环境管理的诸多痛点,确保了环境的一致性、可移植性和快速搭建。无论是个人项目、团队协作还是 CI/CD 流程,将 Docker 融入 Python 开发工作流都能带来显著的收益。

尽管初次接触 Docker 可能需要学习一些新的概念和命令,但投入的时间绝对物有所值。掌握 Docker,你将告别“环境地狱”,将更多精力投入到创造性的编码工作中。开始你的 Docker 化 Python 开发之旅吧!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部