Docker 部署全流程:Dockerfile 与 Docker Compose 编排

📂 所属阶段:第六阶段 — 上线部署(生产篇)
🔗 相关章节:Gunicorn 与 Nginx · 环境变量与安全配置

本地开发一切顺利,一丢到服务器上就各种报错:Python 版本不对、PostgreSQL 连不上、Redis 没装、路径配置全乱……这种“环境不一致地狱”,几乎是每个 Flask 项目上线的必经之痛。

Docker 就是专治这类问题的良药——它把应用、依赖、运行时环境全部打包成一个标准化“镜像”,再通过镜像生成隔离的“容器”,无论在哪台机器上运行,容器内的环境都一模一样。这篇文章会带你从零开始,写出生产可用的 Dockerfile,再用 Docker Compose 编排 Flask + PostgreSQL + Redis + Nginx 的完整服务,并最终在云服务器上完成一键部署。


1. 写好一份高效安全的 Dockerfile

Dockerfile 是一份“镜像构建指南”,每一条指令都会生成一个镜像层。合理的分层顺序不仅可以大幅提升构建速度,还能提高安全性。我们将基于 Python 3.11 的轻量版镜像,一步步构建 Flask 应用。

完整 Dockerfile

# 1. 选择基础镜像:python:3.11-slim 只保留运行必须的系统库,体积小、更适合生产
FROM python:3.11-slim

# 2. 设置工作目录:所有后续操作都在 /app 下进行,避免路径混乱
WORKDIR /app

# 3. 先复制依赖文件,再安装依赖——这是缓存优化的关键!
#    只要 requirements.txt 不变,Docker 就会复用缓存的 pip install 层,
#    不用每次改一行代码都重新下载整个依赖包。
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 4. 最后复制应用代码,代码变更频率最高,放在最下层可以减少重建层
COPY . .

# 5. 安全加固:创建一个非 root 用户,并修改文件权限
RUN useradd --create-home appuser && \
    chown -R appuser:appuser /app
USER appuser

# 6. 声明容器监听端口(仅作文档说明,实际映射通过 docker run 或 Compose 实现)
EXPOSE 8000

# 7. 启动命令:使用 Gunicorn 作为生产级 WSGI 服务器,绝不要用 flask run
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:create_app()"]

分层思路拆解

  • 基础镜像python:3.11-slim 对比完整版去掉了编译工具和文档,体积小,攻击面也更小。
  • 依赖安装:利用 COPY requirements.txt .RUN pip install ... 形成独立的缓存层。日常修改应用代码不会触发依赖层重建,构建时间从几分钟缩短到几秒。
  • 非 root 运行:默认容器内为 root 权限,一旦被攻破可能危及宿主机。切换到普通用户 appuser 是最基本的安全实践。
  • 启动命令:通过 gunicorn 启动 Flask 应用工厂,适合生产环境多进程处理请求。

配套 requirements.txt

该文件列出了 Flask 全家桶以及常用的数据库驱动、异步任务库等,确保项目运行时所有依赖都齐备:

# requirements.txt
Flask>=3.0.0
gunicorn>=21.0.0
flask-sqlalchemy>=3.1.0
flask-login>=0.6.3
flask-wtf>=1.2.0
flask-migrate>=4.0.0
flask-cors>=4.0.0
psycopg2-binary>=2.9.0
redis>=5.0.0
python-dotenv>=1.0.0
email-validator>=2.0.0
Pillow>=10.0.0
celery>=5.3.0

💡 小贴士psycopg2-binary 是预编译版本,无需在容器内安装 PostgreSQL 开发库,非常适合 Docker 场景。


2. 用 Docker Compose 编排多容器

生产环境里,一个 Flask 应用往往还需要数据库、缓存、反向代理等多个服务。手动 docker run 挨个启动不仅繁琐,还很难管理依赖和网络。Docker Compose 让这一切只需要一个 YAML 文件,就能定义所有容器的构建方式、环境变量、数据卷、健康检查以及启动顺序。

2.1 本地开发版:热重载,快速调试

开发时我们需要:

  • 修改代码后自动重载,无需重启容器;
  • 环境变量可以直接写在 Compose 文件里,方便快速启动;
  • 数据库和 Redis 有数据持久化,但允许随时清理。
# docker-compose.yml
version: "3.9"

services:
  # Flask Web 服务
  web:
    build: .                     # 基于当前目录的 Dockerfile 构建
    container_name: daoman_web
    ports:
      - "8000:8000"
    environment:                 # 开发环境直接写入变量
      - FLASK_ENV=development
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/daoman
      - REDIS_URL=redis://redis:6379/0
    volumes:
      - ./app:/app               # 挂载本地代码,实现热重载
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    command: flask run --host=0.0.0.0  # 覆盖 Dockerfile CMD,使用开发服务器

  # PostgreSQL 数据库
  db:
    image: postgres:16-alpine
    container_name: daoman_db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: daoman
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Redis 缓存/消息队列
  redis:
    image: redis:7-alpine
    container_name: daoman_redis

  # Celery 异步任务 Worker
  celery:
    build: .
    container_name: daoman_celery
    command: celery -A app.celery_app worker --loglevel=info
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/daoman
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

volumes:
  postgres_data:   # Docker 管理的卷,数据持久化

关键设计点

  • 代码挂载volumes 将本地的 ./app 目录映射到容器内,任何代码改动都能即时刷新,无需重建镜像。
  • 健康检查depends_on 结合 condition: service_healthy,确保数据库完全就绪后再启动 Web 服务,避免连接失败。
  • 开发服务器command: flask run 方便调试,但实际生产需要替换回 Gunicorn。

2.2 生产环境版:安全、稳定、持久化

生产部署与开发的最大区别在于:

  • 使用预先构建好的镜像,而不是现场 build
  • 环境变量单独放在 .env.production 文件,绝不提交到代码仓库;
  • 所有容器开启 restart: always 保证服务自愈;
  • 加入 Nginx 反向代理,处理静态文件、SSL 证书;
  • 为关键服务配置健康检查。
# docker-compose.prod.yml
version: "3.9"

services:
  web:
    image: daoman/flask:latest         # 从镜像仓库拉取,不再本地构建
    container_name: daoman_web
    restart: always                    # 进程崩溃或宿主机重启后自动拉起
    env_file:
      - .env.production                # 敏感信息放到单独文件
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - postgres_prod:/var/lib/postgresql/data
    env_file:
      - .env.production
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --appendonly yes   # 开启 AOF 持久化,防止数据丢失
    volumes:
      - redis_prod:/data

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static_files:/var/www/static        # 共享静态文件卷
    depends_on:
      - web

volumes:
  postgres_prod:
  redis_prod:
  static_files:

生产配置要点

  • restart: always:无论因为什么原因退出,Docker 都会自动重启容器,配合健康检查可快速恢复服务。
  • 敏感信息外置.env.production 文件通常包含数据库密码、密钥等,通过 .gitignore 排除,杜绝泄露风险。
  • 健康检查:Web 服务定期访问 /health 接口(你需要在应用中简单实现该端点),确认服务处于正常状态。
  • 数据持久化:数据库和 Redis 的数据都存储到命名卷中,即使容器被删除数据也不会丢失。

3. 一键构建与部署流程

本地开发

打开项目根目录,依次执行即可启动完整的开发环境:

# 启动所有服务(-d 表示后台运行)
docker-compose up -d

# 实时查看 Web 服务日志
docker-compose logs -f web

# 修改代码后自动热重载,刷新浏览器就能看到效果

# 停止所有服务(数据保留在卷中)
docker-compose down

# 停止并彻底清除所有数据(清理开发环境)
docker-compose down -v

云服务器生产部署

前提条件:服务器已安装 Docker 和 Docker Compose,项目根目录下存在 docker-compose.prod.yml.env.production 以及 nginx/ 配置目录。

# 1. 拉取最新镜像(如果使用 Docker Hub)
docker-compose -f docker-compose.prod.yml pull

# 2. 首次启动所有服务
docker-compose -f docker-compose.prod.yml up -d

# 3. 如果只是更新了 Web 镜像,可以单独重建并重启 Web 服务(不影响数据库)
docker-compose -f docker-compose.prod.yml up -d --no-deps --build web

# 4. 进入 Web 容器进行调试或数据库迁移
docker exec -it daoman_web /bin/sh
# 进入后执行 flask db upgrade 等命令

🚀 常用技巧:使用 --no-deps 参数重启单个服务时,不会连带重启它依赖的数据库、Redis 等,从而实现零停机滚动更新。


4. 关键最佳实践回顾

  1. 分层缓存优化:Dockerfile 中将 COPY requirements.txt 置于应用代码之前,充分利用层缓存,加快构建。
  2. 安全降权:容器内一律使用非 root 用户运行,缩小潜在攻击面。
  3. 数据持久化:数据库、Redis、静态文件、上传文件等全部挂载到 Docker 卷或主机目录,避免容器销毁时丢失数据。
  4. 健康检查全覆盖:生产环境为所有服务配置 healthcheck,确保依赖链可靠启动。
  5. 敏感信息隔离:生产环境变量、数据库密码等一律放在 .env.production 文件,绝不随着代码一起提交。
  6. 轻量级镜像:基础镜像优先选择 -slim-alpine 版本,既能减小体积,又能降低安全风险。

🔗 扩展阅读

现在,你可以把这份配置直接应用到自己的 Flask 项目中,和“环境不一致”的麻烦彻底说再见。