AI掘金头条项目 Docker Compose 部署完整教程(附踩坑记录)

FastAPI + Vue3 类型项目 Docker 部署完整教程

以 "AI掘金头条" 新闻资讯项目为例,涵盖从零到一的 Docker Compose 部署全流程,包括常见踩坑与解决方案。


快速开始(老手直接看这里)

如果对 Docker 熟悉,直接复制下面文件即可部署。

前提:服务器已装 Docker + Docker Compose,项目目录按下方结构放好。

bash 复制代码
# 1. 确保项目根目录有这些文件:
#    docker-compose.yml、database.sql
#    toutiao_backend/Dockerfile、toutiao_backend/requirements.txt
#    xwzx-news/Dockerfile、xwzx-news/nginx.conf、xwzx-news/.env.production

# 2. 启动
cd /opt/fastapi
docker compose up -d --build

# 3. 验证
curl http://192.168.194.10/api/news/categories

三个最关键的注意点(不搞清楚必踩坑):

  1. api.jsbaseURL?? 不用 ||,否则空字符串会走 fallback
  2. requirements.txt 必须包含 cryptography,否则 MySQL 8.0 连不上
  3. MySQL command 必须加 --skip-character-set-client-handshake,否则中文乱码

下面是从原理到实践的完整讲解。


目录


一、项目架构概览

复制代码
项目根目录/
├── docker-compose.yml              # 服务编排
├── database.sql                    # 数据库初始化脚本
├── toutiao_backend/                # FastAPI 后端
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── main.py
│   ├── config/
│   │   ├── db_config.py            # MySQL 配置
│   │   └── cache_conf.py           # Redis 配置
│   ├── models/                     # ORM 模型
│   ├── routers/                    # API 路由
│   ├── crud/                       # 数据库操作
│   ├── schemas/                    # Pydantic 模型
│   └── utils/                      # 工具函数
└── xwzx-news/                      # Vue3 前端
    ├── Dockerfile
    ├── nginx.conf                  # Nginx 反向代理配置
    ├── .env.production             # 生产环境变量
    ├── vite.config.js
    └── src/
        └── config/
            └── api.js              # API 地址配置

容器拓扑结构:

复制代码
浏览器 ──→ Nginx:80 (frontend)
              │
              ├── /            → 静态文件 (Vue SPA)
              └── /api/*       → 反向代理 → FastAPI:8000 (backend)
                                                  │
                                                  ├── MySQL:3306
                                                  └── Redis:6379

Docker 网络通信原理(理解这个才能排查连接问题):

Docker Compose 会为所有服务创建一个默认网络 ,每个容器自动获得以服务名为域名的内部 DNS。例如:

bash 复制代码
# 在后端容器中,可以直接通过服务名访问其他容器:
ping mysql       # 解析到 MySQL 容器 IP(172.18.0.x)
ping redis       # 解析到 Redis 容器 IP

这就是为什么配置文件里写 DB_HOST=mysqlREDIS_HOST=redis 而不是 IP 地址。

通信场景 地址写法 说明
前端 → 后端(Nginx 代理) http://backend:8000 容器内部通信
后端 → MySQL DB_HOST=mysql 环境变量注入
后端 → Redis REDIS_HOST=redis 环境变量注入
浏览器 → 前端 http://192.168.194.10:80 外部访问,走端口映射
容器内访问宿主机 host.docker.internal 特殊域名(Docker 20.10+)

二、准备工作:让代码适配 Docker

源码中数据库和 Redis 连接地址是硬编码的 localhost,容器化后需要通过环境变量动态注入。

2.1 修改数据库配置

文件:toutiao_backend/config/db_config.py

修改前:硬编码连接字符串

python 复制代码
ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@localhost:3306/news_app?charset=utf8mb4"

修改后:

python 复制代码
import os
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession, create_async_engine

# 从环境变量读取,本地开发用默认值 localhost
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = os.getenv("DB_PORT", "3306")
DB_USER = os.getenv("DB_USER", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "123456")
DB_NAME = os.getenv("DB_NAME", "news_app")

ASYNC_DATABASE_URL = f"mysql+aiomysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4"

# 后续代码保持不变 ...

关键原则os.getenv("KEY", "默认值") 确保本地开发直接 python main.py 也能跑,Docker 部署时通过环境变量覆盖。

2.2 修改 Redis 配置

文件:toutiao_backend/config/cache_conf.py

python 复制代码
import json
import os
from typing import Any
import redis.asyncio as redis

REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
REDIS_DB = int(os.getenv("REDIS_DB", "0"))

# 后续代码保持不变 ...

2.3 修改前端 API 地址配置

文件:xwzx-news/src/config/api.js

核心改动:|| 换成 ??|| 会把空字符串判为 false 导致走误 fallback!

javascript 复制代码
// 开发环境: http://127.0.0.1:8000 (直连后端)
// Docker部署: 空字符串 (Nginx反向代理 /api/ → backend)
export const apiConfig = {
  baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://127.0.0.1:8000',
}

为什么用 ?? 而不是 ||

| 值 | "" || fallback | "" ?? fallback |
|----------------|------------------|------------------|
| "" (空字符串) | fallback ← 错误! | "" ← 正确 |
| undefined | fallback | fallback |
| null | fallback | fallback |
| "http://..." | "http://..." | "http://..." |

Docker 构建时需要把 VITE_API_BASE_URL 设为空字符串 (让请求走同源 Nginx 代理),空字符串是 falsy 值,所以必须用 ??

2.4 添加 Vite 开发代理(可选)

文件:xwzx-news/vite.config.js

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8000',
        changeOrigin: true,
      },
    },
  },
})

加了这个之后,本地 npm run dev 也能用相对路径 /api/xxx,不需要直连 8000 端口,避免跨域问题。

2.5 添加 .dockerignore(加快构建速度)

Docker 构建时会把整个上下文目录发给 Docker daemon。不加 .dockerignore 会把 .venvnode_modules__pycache__ 等大文件也传过去,严重影响构建速度。

文件:toutiao_backend/.dockerignore

复制代码
__pycache__
*.pyc
.venv
.env
.git
.gitignore
*.md
test_*
.idea
cache

文件:xwzx-news/.dockerignore

复制代码
node_modules
dist
.git
.gitignore
*.md
.env
.env.local

注意:.env.production 不能被忽略,构建时需要它。


三、编写 Dockerfile

3.1 后端 Dockerfile

文件:toutiao_backend/Dockerfile

dockerfile 复制代码
FROM python:3.12-slim

WORKDIR /app

# 安装 gcc(bcrypt 编译需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 先复制依赖文件(利用 Docker 缓存层,代码改动时不用重新 pip install)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 再复制应用代码
COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

要点说明:

  • python:3.12-slim --- 体积小,够用
  • gcc --- bcrypt 库编译需要,否则安装会报错
  • COPY requirements.txtCOPY . . --- Docker 分层缓存优化,改代码不用重新装依赖
  • --host 0.0.0.0 --- 必须,否则容器外无法访问

3.2 后端依赖文件

文件:toutiao_backend/requirements.txt

复制代码
fastapi>=0.115.0
uvicorn[standard]>=0.30.0
sqlalchemy[asyncio]>=2.0.0
aiomysql>=0.2.0
redis>=5.0.0
passlib[bcrypt]==1.7.4
bcrypt==3.2.2
pymysql>=1.1.0
cryptography>=41.0.0
python-multipart>=0.0.9

重要cryptography 必须加!MySQL 8.0 默认使用 caching_sha2_password 认证,没有这个包会报 RuntimeError: 'cryptography' package is required

3.3 前端 Dockerfile

文件:xwzx-news/Dockerfile

dockerfile 复制代码
# 阶段一:Node 构建
FROM node:22-alpine AS builder

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .

# VITE_API_BASE_URL 为空字符串 = 前端请求走同源 Nginx 代理
ARG VITE_API_BASE_URL=
RUN VITE_API_BASE_URL=${VITE_API_BASE_URL} npm run build

# 阶段二:Nginx 运行
FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

多阶段构建的好处:

  • 阶段一:Node 环境,只用于编译
  • 阶段二:Nginx 环境,只包含编译产物
  • 最终镜像体积很小(~20MB),不含 node_modules

3.4 Nginx 配置

文件:xwzx-news/nginx.conf

nginx 复制代码
server {
    listen       80;
    server_name  localhost;

    # Vue SPA 静态文件
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
        try_files $uri $uri/ /index.html;  # history 模式路由支持
    }

    # API 反向代理到后端容器
    location /api/ {
        proxy_pass http://backend:8000;     # backend = docker-compose 服务名
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

关键点:

  • try_files $uri $uri/ /index.html --- Vue Router history 模式必须,否则刷新 404
  • proxy_pass http://backend:8000 --- backend 是 docker-compose 中的服务名,Docker 内部 DNS 自动解析
  • 不暴露 8000 端口给外部 --- 所有请求走 80

3.5 前端生产环境变量

文件:xwzx-news/.env.production

复制代码
VITE_API_BASE_URL=

Vite 在 vite build 时自动加载 .env.production,把 VITE_API_BASE_URL 设为空字符串。


四、编写 docker-compose.yml

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

yaml 复制代码
services:
  # ============ MySQL ============
  mysql:
    image: mysql:8.0
    container_name: news_mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: news_app
      MYSQL_CHARSET: utf8mb4
      MYSQL_COLLATION: utf8mb4_unicode_ci
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql            # 数据持久化
      - ./database.sql:/docker-entrypoint-initdb.d/init.sql  # 自动建表
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --init-connect=SET NAMES utf8mb4      # 每个连接也强制 utf8mb4
      - --skip-character-set-client-handshake  # 跳过客户端字符集协商
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
      interval: 10s
      timeout: 5s
      retries: 10

  # ============ Redis ============
  redis:
    image: redis:7-alpine
    container_name: news_redis
    restart: always
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ============ FastAPI 后端 ============
  backend:
    build: ./toutiao_backend
    container_name: news_backend
    restart: always
    ports:
      - "8000:8000"
    depends_on:
      mysql:
        condition: service_healthy   # 等 MySQL 健康检查通过才启动
      redis:
        condition: service_healthy
    environment:
      - DB_HOST=mysql                # 容器名 = 主机名
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=123456
      - DB_NAME=news_app
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  # ============ Vue 前端 ============
  frontend:
    build:
      context: ./xwzx-news
      args:
        VITE_API_BASE_URL: ""        # 空字符串 = 走 Nginx 代理
    container_name: news_frontend
    restart: always
    ports:
      - "80:80"
    depends_on:
      - backend

volumes:
  mysql_data:

配置解读:

配置项 作用
restart: always 容器崩溃自动重启,开机自启
depends_on > condition: service_healthy 等待依赖服务就绪,避免启动顺序问题
volumes: mysql_data 数据卷持久化,容器删除数据不丢
./database.sql:/docker-entrypoint-initdb.d/init.sql MySQL 首次启动自动执行建表
--skip-character-set-client-handshake 跳过客户端字符集协商,强制 utf8mb4

为什么需要 skip-character-set-client-handshake?

MySQL 8.0 容器即使设置了 character-set-server=utf8mb4,客户端连接时仍可能协商成 latin1,导致中文乱码(显示为 央行 等乱码)。加上这个参数彻底锁定字符集。


五、部署到服务器

5.1 服务器环境要求

  • 操作系统:CentOS 7+ / Red Hat / Ubuntu
  • 已安装 Docker 和 Docker Compose
bash 复制代码
# Red Hat / CentOS 安装 Docker
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 启动
sudo systemctl start docker
sudo systemctl enable docker

5.2 传输项目文件

将整个项目目录上传到服务器(排除 node_modules.venv__pycache__):

bash 复制代码
# 本地先清理
rm -rf xwzx-news/node_modules toutiao_backend/.venv toutiao_backend/__pycache__

# 上传到服务器
scp -r ./fastapi root@192.168.194.10:/opt/

5.3 一键启动

bash 复制代码
cd /opt/fastapi
docker compose up -d --build

首次启动 MySQL 会自动执行 database.sql 建表(约 1-2 分钟)。

5.4 常用管理命令

bash 复制代码
# 查看运行状态
docker compose ps

# 查看日志
docker compose logs -f              # 所有服务
docker compose logs -f backend      # 指定服务

# 重启
docker compose restart

# 停止
docker compose stop

# 停止并删除容器(保留数据)
docker compose down

# 停止并删除容器 + 数据卷(⚠ 数据库数据会丢失)
docker compose down -v

# 重新构建并启动
docker compose up -d --build

# 只重建单个服务
docker compose build --no-cache frontend
docker compose up -d frontend

5.5 更新代码后如何重新部署

日常开发中改代码 → 重新部署是最频繁的操作。不同改动的更新方式不同:

只改了前端代码(页面、样式、API 调用):

bash 复制代码
cd /opt/fastapi
docker compose build --no-cache frontend
docker compose up -d frontend

只改了后端代码(路由、CRUD、逻辑):

bash 复制代码
cd /opt/fastapi
docker compose build --no-cache backend
docker compose up -d backend

改了 requirements.txt 或 Dockerfile(依赖变更):

bash 复制代码
cd /opt/fastapi
docker compose build --no-cache backend
docker compose up -d backend

改了 database.sql(数据库结构变更):

bash 复制代码
cd /opt/fastapi
docker compose down -v        # 删数据卷重建
docker compose up -d --build

改了 docker-compose.yml(服务配置变更):

bash 复制代码
cd /opt/fastapi
docker compose up -d --build  # Docker 会自己判断哪些需要重建

小技巧:如果不是确定需要 --no-cache,先试 docker compose up -d --build,它只重建有变化的层,快很多。



六、验证与测试

6.1 浏览器访问

复制代码
http://192.168.194.10        → 前端页面
http://192.168.194.10/api/   → 后端接口(通过 Nginx 代理)
http://192.168.194.10:8000/  → 后端接口(直连)

6.2 命令行接口测试

bash 复制代码
# 测试 API
curl http://192.168.194.10/api/news/categories

# 测试注册
curl -X POST http://192.168.194.10/api/user/register \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"123456"}'

# 测试 + 查看后端日志(排查问题用)
curl -v -X POST http://192.168.194.10/api/user/register \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"123456"}'; \
  docker logs news_backend --tail 20

6.3 验证数据库

bash 复制代码
# 进入 MySQL 容器
docker exec -it news_mysql mysql -uroot -p123456 news_app

# 查看表
SHOW TABLES;

# 验证中文字符集
SHOW VARIABLES LIKE 'character%';

七、常见踩坑与解决

坑 1:cryptography package is required

复制代码
RuntimeError: 'cryptography' package is required for sha256_password or caching_sha2_password auth methods

原因 :MySQL 8.0 默认认证插件 caching_sha2_password 需要 cryptography 包。

解决requirements.txt 加入 cryptography>=41.0.0


坑 2:前端请求 127.0.0.1:8000 连接拒绝

复制代码
Failed to load resource: net::ERR_CONNECTION_REFUSED
http://127.0.0.1:8000/api/user/register

原因 :前端构建时 VITE_API_BASE_URL 没生效,走了 fallback 值。

排查方法

bash 复制代码
# 检查构建产物中是否还有 127.0.0.1
docker exec news_frontend sh -c "grep -r '127.0.0.1:8000' /usr/share/nginx/html/"

根因api.js 中用了 || 运算符:

javascript 复制代码
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8000'
//      ^^ 空字符串是 falsy,会走 fallback!

解决 :改用 ??(空值合并运算符):

javascript 复制代码
baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://127.0.0.1:8000'
//      ^^ 只有 null/undefined 才走 fallback

坑 3:中文乱码(央行宣å¸)

原因 :MySQL 容器字符集协商问题,客户端连接降级为 latin1

排查方法

bash 复制代码
docker exec news_mysql mysql -uroot -p123456 -e "SHOW VARIABLES LIKE 'character%';"

解决docker-compose.yml MySQL command 加两行:

yaml 复制代码
command:
  - --character-set-server=utf8mb4
  - --collation-server=utf8mb4_unicode_ci
  - --init-connect=SET NAMES utf8mb4       # 新增
  - --skip-character-set-client-handshake   # 新增

然后重建(⚠ 会清空数据):

bash 复制代码
docker compose down -v
docker compose up -d --build

坑 4:Docker 构建缓存导致修改不生效

现象:改了代码但构建后还是旧逻辑。

解决

bash 复制代码
# 强制无缓存重建
docker compose build --no-cache frontend
docker compose up -d frontend

坑 5:bcrypt 版本兼容性问题

Python 3.12 + bcrypt 最新版可能编译失败或 API 不兼容。

解决:锁定版本:

复制代码
passlib[bcrypt]==1.7.4
bcrypt==3.2.2

坑 6:Vue Router 刷新 404

原因 :Nginx 没有配置 try_files,非根路径刷新时找不到文件。

解决nginx.conf 中:

nginx 复制代码
location / {
    try_files $uri $uri/ /index.html;  # 这句必须有
}

附录:文件清单与路径

文件 路径 说明
docker-compose.yml 项目根目录 服务编排
Dockerfile(后端) toutiao_backend/Dockerfile FastAPI 镜像
requirements.txt toutiao_backend/requirements.txt Python 依赖
db_config.py toutiao_backend/config/db_config.py 需改环境变量
cache_conf.py toutiao_backend/config/cache_conf.py 需改环境变量
Dockerfile(前端) xwzx-news/Dockerfile 多阶段构建
nginx.conf xwzx-news/nginx.conf Nginx 代理
.env.production xwzx-news/.env.production Vite 生产环境变量
api.js xwzx-news/src/config/api.js `
vite.config.js xwzx-news/vite.config.js 开发代理配置
.dockerignore(后端) toutiao_backend/.dockerignore 排除 .venv 等大文件
.dockerignore(前端) xwzx-news/.dockerignore 排除 node_modules
database.sql 项目根目录 数据库建表脚本

附录 B:移植到自己的项目

这份配置不是绑定这个新闻项目的。如果你的项目也是 FastAPI 后端 + Vue3 前端 + MySQL + Redis,改动清单如下:

需要改的 文件 改什么
数据库密码 docker-compose.yml MYSQL_ROOT_PASSWORDDB_PASSWORD、healthcheck 中的密码
数据库连接 db_config.py 如果表结构不同,改 DB_NAME
后端端口 Dockerfiledocker-compose.ymlnginx.conf 如果后端不是 8000
API 路由前缀 nginx.confvite.config.js 如果 API 前缀不是 /api/
项目路径 docker-compose.yml build: ./你的后端目录build: ./你的前端目录
前端依赖 xwzx-news/Dockerfile 如果有额外依赖需要安装
数据库初始化 database.sql 换成你自己的建表 SQL

非 MySQL?requirements.txt 中的数据库驱动(如 asyncpg 替代 aiomysql),修改 db_config.py 中的连接字符串格式,改 docker-compose.yml 中数据库镜像。

非 Redis? 把相关配置删掉即可,这个项目对 Redis 是弱依赖(缓存)。


以上就是从源码到 Docker 部署的完整流程。核心思路:环境变量注入配置 → Dockerfile 定义镜像 → docker-compose 编排服务 → 一键部署。遇到问题先看日志,大部分问题都是环境变量或字符集配置引起的。

相关推荐
Nightwish51 小时前
Linux随记(三十)
linux·运维·mysql·ambari
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年5月2日
人工智能·python·信息可视化·自然语言处理·ai编程
cui_ruicheng1 小时前
Linux信号机制(一):从概念到产生与处理
linux·运维·服务器
qyzm1 小时前
Codeforces Round 1073 (Div. 2)
数据结构·python·算法
JK Chen1 小时前
faster_whisper,视频转文字,并生成字幕文件
python·whisper·音视频
Victor3562 小时前
MongoDB(118)如何在升级过程中进行数据备份?
后端
手握风云-2 小时前
Spring AI:让大模型住进 Spring 生态(三)
java·后端·spring
Victor3562 小时前
MongoDB(117)如何从旧版本迁移到新版本?
后端
KnowSafe3 小时前
从手动到智能:证书自动化解决方案的技术演进
运维·自动化