跳到主要内容

05 全栈项目部署

压轴实战:把前面三篇的内容组合成一套完整的生产部署——前端 + 后端 + MySQL + Redis + Nginx 反向代理,一个 compose.yaml 全部拉起。

1. 整体架构​

设计原则:

  • 只有 nginx 对外开放端口,其余服务全部藏在容器网络里
  • 前后端同域名(/api/ 前缀走后端),天然没有跨域问题

2. 项目结构​

myproject/
├── backend/ # FastAPI 项目
│ ├── app/
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/ # 前端项目
│ ├── src/
│ ├── package.json
│ └── Dockerfile
├── nginx/
│ └── default.conf # 反向代理配置
├── mysql/
│ └── init/
│ └── 01-schema.sql
├── .env # 生产密钥(不进 Git)
├── .env.example
└── compose.yaml

3. 各服务的 Dockerfile​

backend/Dockerfile(同上一篇的生产级写法):

FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 TZ=Asia/Shanghai
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
COPY . .
RUN useradd -m appuser && chown -R appuser /app
USER appuser
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

frontend/Dockerfile(多阶段构建,产物给 nginx 服务用——注意这里最终镜像只是个「静态文件载体」):

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --registry=https://registry.npmmirror.com
COPY . .
RUN npm run build

# 产物放进一个极小的镜像,由 nginx 服务通过卷共享读取
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html

4. Nginx 反向代理配置​

nginx/default.conf:

server {
listen 80;
server_name _;

# 前端静态文件
root /usr/share/nginx/html;
index index.html;

gzip on;
gzip_types text/css application/javascript application/json;

# /api/ 开头的请求转发给后端容器
location /api/ {
proxy_pass http://api:8000/; # api 是服务名;结尾的 / 会去掉 /api 前缀
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;
}

# SPA 路由兜底
location / {
try_files $uri $uri/ /index.html;
}
}

proxy_pass http://api:8000/; 结尾带 /:/api/users 会被转发成 /users。如果后端路由本身就带 /api 前缀,就把结尾的 / 去掉。

5. 总装:compose.yaml​

services:
nginx:
build: ./frontend # 前端镜像里已带静态文件和 Nginx
restart: unless-stopped
ports:
- "80:80" # 整个项目唯一对外的端口
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
logging:
options:
max-size: "10m"
max-file: "3"

api:
build: ./backend
restart: unless-stopped
# 没有 ports!只有 nginx 能通过容器网络访问它
env_file:
- .env
environment:
DATABASE_URL: mysql+pymysql://appuser:${MYSQL_USER_PASSWORD}@mysql:3306/appdb
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
logging:
options:
max-size: "10m"
max-file: "3"

mysql:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD}
TZ: Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s

redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 5s
timeout: 3s
retries: 10

volumes:
mysql-data:
redis-data:

.env.example:

MYSQL_ROOT_PASSWORD=change-me
MYSQL_USER_PASSWORD=change-me
REDIS_PASSWORD=change-me
SECRET_KEY=change-me

6. 上线步骤​

# 1. 服务器上拉代码
git clone https://github.com/you/myproject.git /srv/myproject
cd /srv/myproject

# 2. 准备生产配置
cp .env.example .env
vim .env # 填入真实密码

# 3. 一键启动
docker compose up -d --build

# 4. 检查
docker compose ps # 四个服务全部 Up / Up (healthy)
docker compose logs -f api # 后端启动日志无报错
curl -I http://localhost # 前端 200
curl http://localhost/api/health # 后端 {"status":"ok"}

浏览器访问服务器 IP(或解析好的域名),整套系统就跑起来了。

7. 日常运维​

# 发新版
git pull
docker compose up -d --build

# 只重启后端
docker compose restart api

# 数据库迁移
docker compose exec api alembic upgrade head

# 看某个服务日志
docker compose logs -f --tail 200 api

# 数据库备份(配 crontab 自动化)
docker compose exec -T mysql mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" appdb | gzip > /backup/appdb_$(date +%F).sql.gz

8. 可选强化​

  • HTTPS:在 nginx 服务上挂证书(用 certbot 签发后把 fullchain.pem/privkey.pem 挂载进去,监听 443)
  • CI/CD:用 GitHub Actions / Jenkins 在提交后自动构建镜像并在服务器上执行 docker compose up -d
  • 监控:docker stats 起步,规模大了再上 Prometheus + Grafana
  • 镜像仓库:团队协作时把镜像推到私有仓库(Harbor / 阿里云 ACR),服务器只 pull 不 build

小结​

  • 全栈部署的核心思想:只暴露 Nginx,其余服务藏进容器网络
  • 服务间连接一律用服务名:mysql、redis、api
  • depends_on + healthcheck 保证「数据库就绪后端才启动」
  • 一台新服务器从零到上线只需四步:装 Docker → 拉代码 → 配 .env → up -d --build

恭喜你完成了全部实战!遇到问题时查阅 附录:常见问题排查。