「在我的机器上可以跑」是部署的原罪。本文将 Python 应用从开发环境「封印」到生产环境,确保任何机器上行为一致。
1. Docker 多阶段构建
1.1 基础 Dockerfile
# ========== Stage 1: Builder ==========
FROM python:3.12-slim as builder
WORKDIR /app
# 安装构建依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 安装 Python 依赖到独立目录
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# ========== Stage 2: Production ==========
FROM python:3.12-slim
WORKDIR /app
# 创建非 root 用户
RUN useradd -m -u 1000 appuser
# 只复制必要的依赖
COPY --from=builder /root/.local /home/appuser/.local
COPY ./app ./app
# 设置环境
ENV PATH=/home/appuser/.local/bin:$PATH \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
1.2 基于 uv 的优化版
# 最小化镜像(基于 uv 官方镜像)
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
# 启用编译缓存
ENV UV_COMPILE_BYTECODE=1
# 先复制 lock 文件(利用 Docker 层缓存)
COPY uv.lock pyproject.toml ./
RUN uv sync --frozen --no-install-project --no-dev
# 再复制代码
COPY ./app ./app
RUN uv sync --frozen --no-dev
# 运行
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
镜像大小对比:
| 方案 | 大小 | 说明 |
|---|---|---|
| 单阶段(全量) | 1.2GB | 包含 gcc 等构建工具 |
| 多阶段 pip | 180MB | 分离构建与运行 |
| 多阶段 uv | 85MB | 最小化依赖 |
| distroless | 65MB | 无 shell,最安全 |
1.3 健康检查与优雅关闭
# Dockerfile 中添加
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
# app/main.py — 优雅关闭处理
import asyncio
import signal
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# 启动
await connect_db()
yield
# 关闭
await close_db()
app = FastAPI(lifespan=lifespan)
@app.get("/health")
async def health():
return {"status": "healthy"}
2. 服务器配置:uvicorn vs gunicorn
2.1 选型
| 服务器 | 适用场景 | 特点 |
|---|---|---|
| uvicorn | 开发、单进程服务 | 原生 ASGI,最简单 |
| gunicorn + uvicorn workers | 生产多核 | 进程管理 + 异步处理 |
| hypercorn | HTTP/2、WebSocket | 协议支持最全 |
| daphne | Django Channels | Django 生态 |
2.2 生产配置
# gunicorn + uvicorn(推荐)
gunicorn app.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--access-logfile - \
--error-logfile - \
--log-level info \
--timeout 120 \
--keep-alive 5 \
--max-requests 10000 \
--max-requests-jitter 1000
# 或使用配置文件
cat > gunicorn.conf.py << 'EOF'
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 120
keepalive = 5
errorlog = "-"
accesslog = "-"
EOF
gunicorn -c gunicorn.conf.py app.main:app
2.3 systemd 服务
# /etc/systemd/system/myapp.service
[Unit]
Description=My Python App
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment=PATH=/opt/myapp/.venv/bin
Environment=DATABASE_URL=postgresql://...
ExecStart=/opt/myapp/.venv/bin/gunicorn -c gunicorn.conf.py app.main:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl restart myapp
3. 打包独立可执行文件
3.1 PyInstaller
pip install pyinstaller
# 单文件打包
pyinstaller --onefile --name mytool app/cli.py
# 带图标和数据文件
pyinstaller \
--onefile \
--name mytool \
--icon=assets/icon.ico \
--add-data "config.yaml:." \
app/cli.py
# 输出在 dist/mytool
3.2 uv 打包(实验性)
# uv 支持将项目打包为 zipapp(单文件,无需安装)
uv pip install . --target dist/
python -m zipapp dist/ -p "/usr/bin/env python3" -o myapp.pyz
# 运行
python myapp.pyz
3.3 平台分发对比
| 工具 | 输出 | 大小 | 跨平台 | 场景 |
|---|---|---|---|---|
| PyInstaller | 单二进制 | 10-50MB | ❌ 需各平台构建 | GUI/CLI 工具 |
| zipapp | .pyz | 小 | ✅ | 纯 Python 脚本 |
| Docker | 容器镜像 | 大 | ✅ | 服务端部署 |
| nuitka | C 编译 | 较小 | ❌ | 性能敏感 |
4. PyPI 发布
4.1 项目结构
my-package/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── core.py
└── tests/
└── test_core.py
4.2 pyproject.toml 发布配置
[project]
name = "my-package"
version = "0.1.0"
description = "A useful Python package"
readme = "README.md"
license = { text = "MIT" }
authors = [
{ name = "Your Name", email = "you@example.com" }
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
keywords = ["utility", "tool"]
requires-python = ">=3.11"
dependencies = ["requests>=2.30", "pydantic>=2.0"]
[project.optional-dependencies]
dev = ["pytest", "mypy", "ruff"]
[project.urls]
Homepage = "https://github.com/you/my-package"
Repository = "https://github.com/you/my-package.git"
Issues = "https://github.com/you/my-package/issues"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/my_package"]
4.3 构建与上传
# 安装构建工具
pip install build twine
# 构建(生成 dist/*.whl 和 dist/*.tar.gz)
python -m build
# 检查
python -m twine check dist/*
# 上传到 TestPyPI(测试)
python -m twine upload --repository testpypi dist/*
# 上传到正式 PyPI
python -m twine upload dist/*
4.4 GitHub Actions 自动发布
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
pypi-publish:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC 认证,无需 API token
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Sync
run: uv sync
- name: Build
run: uv run python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
设置 PyPI Trusted Publisher:
- PyPI 项目设置 → Publishing
- Add a new pending publisher
- 填写 GitHub 仓库和 workflow 文件名
- 无需存储 API token!
5. Nix:终极可复现环境
5.1 基础 flake.nix
{
description = "My Python App";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
python = pkgs.python312;
in
{
devShells.default = pkgs.mkShell {
packages = [
python
python.pkgs.uv
python.pkgs.ruff
python.pkgs.mypy
];
};
packages.default = python.pkgs.buildPythonApplication {
pname = "myapp";
version = "0.1.0";
src = ./.;
propagatedBuildInputs = [
python.pkgs.fastapi
python.pkgs.uvicorn
];
};
});
}
使用:
# 进入开发环境(所有依赖精确锁定)
nix develop
# 构建
nix build
# 运行
./result/bin/myapp
6. 部署清单
6.1 生产检查表
- 使用非 root 用户运行
- 启用 HEALTHCHECK
- 配置日志轮转(logrotate)
- 设置资源限制(ulimit, cgroups)
- 配置反向代理(Nginx/Traefik)
- 启用 HTTPS(Let’s Encrypt)
- 设置环境变量(不硬编码密钥)
- 配置监控(Prometheus metrics)
- 配置告警(CPU/内存/磁盘)
6.2 docker-compose 生产模板
version: '3.8'
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
deploy:
replicas: 2
resources:
limits:
cpus: '1.0'
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
volumes:
postgres_data:
redis_data:
延伸阅读
- Python Web 框架 — FastAPI 应用开发
- Python 现代工具链 — uv、Poetry 与构建系统
- Docker 容器化最佳实践 — 通用 Docker 优化技巧
- GitHub Actions CI/CD — 自动化测试到部署的完整流水线
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。
「python」更多文章
Python 测试与质量工程:pytest、mock 与覆盖率实战
Python 测试金字塔完整实践:pytest 核心(fixture/parametrize/monkeypatch)、unittest.mock/patch、Monkeypatch、覆盖率 pytest-cov、类型测试、CI 集成策略与 doctest。覆盖从单元测试到集成测试的完整工程方案。
Python 现代工具链:uv + ruff + mypy 全链路工程实践
Python 工具链现代化完整指南:uv(极速包管理+虚拟环境+Python 安装)、ruff(lint+format 一体化)、mypy/pyright 类型检查、pipx 工具安装、 hatch/poetry/pdm 项目管理、从 pip 到 uv 的迁移路径。附带 pyproject.toml 完整配置模板。
Python 数据科学与 AI:Pandas/Polars、PyTorch 推理与 ONNX 部署
Python 数据科学生态全景:Pandas vs Polars vs NumPy 选型与性能对比、PyTorch 模型推理与优化、Transformers pipeline 实战、ONNX 导出与跨平台推理、与 Rust(PyO3)互操作加速。覆盖从数据处理到生产部署的完整链路。