Python与Docker
Python容器化
什么是 Docker
Docker 是一种容器化技术,它可以把你的应用和所有依赖打包到一个标准化的容器中。这个容器可以在任何安装了 Docker 的机器上一致地运行,不会出现”在我电脑上能跑”的问题。
对于 Python 开发者来说,Docker 解决了几个核心痛点:不同机器上 Python 版本不一致、系统依赖缺失、开发环境与生产环境差异导致的故障。通过 Docker,你可以确保代码在开发、测试、生产环境中以完全相同的方式运行。
基础概念
镜像(Image)
镜像是一个只读的模板,包含了运行应用所需的一切:操作系统、Python 解释器、第三方库、代码文件等。你可以把镜像理解为一份”安装光盘”,用它来创建容器。
容器(Container)
容器是镜像的运行实例。一个镜像可以创建多个容器,每个容器都是独立运行的、隔离的进程。容器启动很快,占用的资源也远少于虚拟机。
Dockerfile
Dockerfile 是一个文本文件,包含了一系列指令,用来告诉 Docker 如何构建镜像。每一条指令都会在镜像中创建一个新的层。
Docker Compose
Docker Compose 是一个用于定义和运行多容器应用的工具。通过一个 YAML 文件,你可以同时启动应用、数据库、缓存等多个服务,非常适合本地开发。
快速上手
安装 Docker
前往 Docker 官网下载并安装 Docker Desktop。安装完成后在终端验证:
# 验证 Docker 是否安装成功
docker --version
# 验证 Docker 是否正常运行
docker run hello-world
最简单的 Python 容器
创建一个文件名为 Dockerfile 的文件(没有扩展名):
# 使用官方 Python 镜像作为基础
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 复制当前目录的所有文件到容器中
COPY . .
# 运行 Python 脚本
CMD ["python", "app.py"]
在同目录下创建 app.py:
# 一个简单的 Python 脚本
print("Hello from Docker!")
构建并运行:
# 构建镜像,-t 参数给镜像起个名字
docker build -t my-python-app .
# 运行容器
docker run my-python-app
详细用法
编写规范的 Dockerfile
一个生产级别的 Python 项目 Dockerfile 通常包含以下步骤:
# 第一阶段:构建阶段
FROM python:3.12-slim AS builder
# 设置工作目录
WORKDIR /app
# 先只复制依赖文件(利用 Docker 缓存层机制)
COPY requirements.txt .
# 安装依赖到指定目录
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 第二阶段:运行阶段(最终镜像)
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 从构建阶段复制已安装的依赖
COPY --from=builder /install /usr/local
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8000
# 使用非 root 用户运行(安全最佳实践)
RUN useradd --create-home appuser
USER appuser
# 启动命令
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
理解 Docker 缓存层
Docker 构建镜像时,每一条指令都会生成一个层(layer)。如果某条指令的内容没有变化,Docker 会复用之前的缓存层,不会重新执行。这就是为什么要把 COPY requirements.txt 和 RUN pip install 放在 COPY . . 之前——只要依赖没变,安装依赖这一步就会使用缓存,大幅加快构建速度。
# 正确的顺序:先复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 最后再复制代码(代码经常变动,放最后)
COPY . .
使用 .dockerignore
和 .gitignore 类似,.dockerignore 文件指定哪些文件不复制到镜像中:
# .dockerignore 文件内容
__pycache__
*.pyc
*.pyo
.git
.gitignore
.venv
venv
.env
*.md
.pytest_cache
.mypy_cache
.ruff_cache
这样可以避免把不必要的文件打包进镜像,减小镜像体积,也防止敏感信息泄露。
环境变量与配置
在 Dockerfile 中可以通过 ENV 指令设置环境变量:
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV APP_ENV=production
# PYTHONUNBUFFERED=1 让 Python 日志实时输出(不缓冲)
# PYTHONDONTWRITEBYTECODE=1 不生成 .pyc 文件
运行容器时也可以通过 -e 参数传入环境变量:
# 通过 -e 参数设置环境变量
docker run -e DATABASE_URL=postgresql://db:5432/mydb my-app
# 通过 --env-file 从文件加载环境变量
docker run --env-file .env my-app
端口映射与数据卷
# 端口映射:将容器内的 8000 端口映射到主机的 8000 端口
docker run -p 8000:8000 my-app
# 数据卷:将主机目录挂载到容器内(代码修改实时生效)
docker run -v $(pwd):/app my-app
# 组合使用
docker run -p 8000:8000 -v $(pwd):/app my-app
Docker Compose 管理多服务
实际项目中,你的应用通常需要数据库、缓存等服务。Docker Compose 可以用一个配置文件定义所有服务。
创建 docker-compose.yml:
# Docker Compose 配置文件
version: '3.8'
services:
# Web 应用服务
web:
build: .
ports:
- '8000:8000'
volumes:
- .:/app
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
# PostgreSQL 数据库服务
db:
image: postgres:16
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- '5432:5432'
# Redis 缓存服务
redis:
image: redis:7-alpine
ports:
- '6379:6379'
# 命名数据卷
volumes:
postgres_data:
使用 Docker Compose 的常用命令:
# 构建并启动所有服务(后台运行)
docker compose up -d
# 查看运行中的服务
docker compose ps
# 查看服务日志
docker compose logs web
# 进入容器内部执行命令
docker compose exec web bash
# 停止所有服务
docker compose down
# 停止并删除数据卷(重置数据库)
docker compose down -v
开发环境与生产环境的 Dockerfile
开发环境需要支持热重载、调试,生产环境需要更小的镜像和更高的安全性。
开发用 Dockerfile:
# 开发环境 Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 安装开发依赖
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt
COPY . .
# 使用热重载模式启动
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
生产用 Dockerfile(多阶段构建):
# 生产环境 Dockerfile - 多阶段构建
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
常见场景
FastAPI 项目容器化
项目结构:
myproject/
app/
__init__.py
main.py
models.py
requirements.txt
Dockerfile
docker-compose.yml
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Celery Worker 容器化
如果你的项目使用 Celery 处理异步任务,需要为 Worker 单独创建容器。可以在 docker-compose.yml 中添加:
# Celery Worker 服务
worker:
build: .
command: celery -A app.celery worker --loglevel=info
volumes:
- .:/app
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
# Celery Beat 定时任务服务
beat:
build: .
command: celery -A app.celery beat --loglevel=info
volumes:
- .:/app
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
数据科学项目容器化
# 数据科学项目的 Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 安装系统依赖(某些数据科学库需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Jupyter Notebook
EXPOSE 8888
CMD ["jupyter", "notebook", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root"]
注意事项与常见错误
不要把敏感信息写进镜像
密码、API Key、数据库连接字符串等敏感信息绝对不能写在 Dockerfile 中。应该通过环境变量在运行时传入:
# 正确做法:运行时传入敏感信息
docker run -e SECRET_KEY=mysecret -e DB_PASSWORD=mypassword my-app
镜像体积过大
常见原因和解决方法:
- 使用了完整版基础镜像(python:3.12 而不是 python:3.12-slim),换成 slim 版本
- 没有使用 .dockerignore,把不必要的文件打包进去了
- 没有使用多阶段构建,构建工具也被包含在最终镜像中
- pip 缓存没有清理,使用 —no-cache-dir 参数
容器内时区问题
默认情况下容器使用 UTC 时区,如果你的应用依赖时区,需要设置:
# 设置时区为上海
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
文件权限问题
容器内默认以 root 用户运行,这可能导致挂载卷时文件权限混乱。建议创建专用用户:
# 创建非 root 用户
RUN useradd --create-home appuser
# 确保用户对工作目录有权限
RUN chown -R appuser:appuser /app
USER appuser
容器间通信
在 Docker Compose 中,服务之间通过服务名访问,而不是 localhost。例如 web 服务访问 db 服务,应该用 db:5432 而不是 localhost:5432:
# 正确:使用 Docker Compose 中的服务名
DATABASE_URL = "postgresql://postgres:password@db:5432/mydb"
# 错误:不能用 localhost
# DATABASE_URL = "postgresql://postgres:password@localhost:5432/mydb"
Windows 下的路径问题
在 Windows 上使用 Docker 时,路径分隔符和换行符可能有问题。建议:
- Dockerfile 使用 LF 换行符(不要用 CRLF)
- 挂载卷时使用正斜杠或 (pwd)
进阶用法
多阶段构建减小镜像体积
多阶段构建是减小镜像体积的最有效手段。核心思路是:在构建阶段安装编译工具和依赖,在运行阶段只复制编译好的结果:
# 阶段一:构建
FROM python:3.12 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 阶段二:运行
FROM python:3.12-slim
WORKDIR /app
# 只复制安装好的依赖,不包含构建工具
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "app.py"]
健康检查
Docker 支持在 Dockerfile 中定义健康检查,让 Docker 自动判断容器是否正常运行:
# 定义健康检查:每 30 秒检查一次
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
也可以在 docker-compose.yml 中定义:
services:
web:
build: .
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:8000/health']
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
使用 uv 加速依赖安装
uv 是一个极速的 Python 包管理器,可以替代 pip:
FROM python:3.12-slim
# 安装 uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
# 使用 uv 安装依赖(比 pip 快 10-100 倍)
COPY pyproject.toml .
RUN uv pip install --system --no-cache -r pyproject.toml
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Docker 多平台构建
如果你的镜像需要在不同 CPU 架构上运行(比如 Intel 和 Apple Silicon),可以使用多平台构建:
# 创建多平台构建器
docker buildx create --name mybuilder --use
# 构建多平台镜像
docker buildx build --platform linux/amd64,linux/arm64 -t my-app .