摘要:Docker已经是开发者必备技能。本文从零开始,手把手教你把一个Python Web应用容器化,涵盖Dockerfile编写、镜像优化、docker-compose编排、常见踩坑等实战内容。
为什么要用Docker?
一句话:"在我机器上能跑"不再是借口。
Docker把应用和它的所有依赖打包成一个标准化的容器,在任何环境都能一致运行。
安装Docker
# Ubuntu
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# 验证
docker --version
docker run hello-world
第一个Dockerfile
假设我们有一个FastAPI应用:
myapp/
├── app/
│ ├── __init__.py
│ └── main.py
├── requirements.txt
└── Dockerfile
app/main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def root():
return {'message': 'Hello Docker!'}
@app.get('/health')
def health():
return {'status': 'ok'}
requirements.txt:
fastapi==0.109.0
uvicorn==0.27.0
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
# 先复制依赖文件,利用Docker缓存
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"]
构建和运行:
docker build -t myapp .
docker run -d -p 8000:8000 --name myapp myapp
curl http://localhost:8000/health
Dockerfile最佳实践
1. 多阶段构建(减小镜像体积)
# 构建阶段
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 app/ ./app/
# 非root用户运行
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
2. 利用构建缓存
Docker每一层都有缓存。把变化频率低的放前面:
# ✅ 好:依赖不常变,源码常变
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app/ ./app/
# ❌ 差:每次改代码都要重新装依赖
COPY . .
RUN pip install -r requirements.txt
3. .dockerignore
.git
__pycache__
*.pyc
.env
.venv
node_modules
*.md
.pytest_cache
docker-compose编排
真实项目通常需要多个服务(应用+数据库+缓存):
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- '8000:8000'
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U user']
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
常用命令:
docker-compose up -d # 启动所有服务
docker-compose logs -f app # 查看应用日志
docker-compose down # 停止并删除容器
docker-compose down -v # 连数据卷一起删
docker-compose exec app bash # 进入容器
镜像体积优化
# 查看镜像大小
docker images myapp
| 基础镜像 | 最终大小 | 说明 |
|---|---|---|
| python:3.12 | ~1.0GB | 完整版,太大 |
| python:3.12-slim | ~150MB | 精简版,推荐 |
| python:3.12-alpine | ~60MB | 最小,但可能有兼容问题 |
Alpine的坑:很多Python包需要编译C扩展,Alpine用musl而不是glibc,可能导致编译失败或性能下降。建议用slim。
常用Docker命令
# 镜像管理
docker build -t myapp:v1 . # 构建
docker tag myapp:v1 registry/myapp:v1 # 打标签
docker push registry/myapp:v1 # 推送
# 容器管理
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器
docker logs -f --tail 100 myapp # 查看日志
docker exec -it myapp bash # 进入容器
docker stats # 资源使用情况
# 清理
docker system prune -a # 清理所有未使用的资源
docker volume prune # 清理未使用的卷
环境变量管理
# Dockerfile中设置默认值
ENV APP_ENV=production
ENV LOG_LEVEL=info
# 运行时覆盖
docker run -e APP_ENV=development myapp
# 用env文件
docker run --env-file .env myapp
# docker-compose中用.env文件
services:
app:
env_file:
- .env
- .env.local # 本地覆盖
健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
常见踩坑
1. 容器内访问宿主机服务
# ❌ 容器内localhost是容器自己
requests.get('http://localhost:3306')
# ✅ 用特殊域名
requests.get('http://host.docker.internal:3306')
2. 时区问题
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
3. 文件权限
# 创建非root用户
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
RUN chown -R appuser:appgroup /app
USER appuser
4. 日志输出
# Python在容器中要禁用输出缓冲
# Dockerfile中加:
ENV PYTHONUNBUFFERED=1
生产部署清单
- 使用slim基础镜像
- 多阶段构建减小体积
- 非root用户运行
- 配置健康检查
- 设置资源限制(CPU/内存)
- 日志输出到stdout
- 敏感信息用环境变量,不要写在镜像里
- .dockerignore排除无关文件
- 固定依赖版本(pip freeze)
总结
Docker的核心价值是环境一致性和部署标准化。掌握Dockerfile编写、compose编排、镜像优化这三板斧,就能应对大部分场景。
建议从自己的一个小项目开始练手,把它容器化部署一遍,比看十篇教程都有用。