Skip to content

Docker 构建

本地开发用 uvicorn --reloadnpm run dev,部署到服务器时要用 Docker 打包——把应用和依赖装进镜像,换台机器 docker run 就能跑,不用关心目标机器装了什么。

ops-console 前后端分开打包:后端一个镜像(Python + FastAPI + 依赖),前端一个镜像(打包后的静态文件 + Nginx 提供服务)。

一、后端 Dockerfile

dockerfile
# backend/Dockerfile

# ---- 构建阶段:安装依赖 ----
FROM python:3.12-slim AS builder

WORKDIR /app

# 先拷依赖文件(利用 Docker 缓存:代码变了不用重装依赖)
COPY pyproject.toml uv.lock ./

# 安装 uv 并用它装依赖到虚拟环境
RUN pip install uv && \
    uv sync --frozen --no-dev

# 拷代码
COPY . .

# ---- 运行阶段:最小镜像 ----
FROM python:3.12-slim

WORKDIR /app

# 从构建阶段拷虚拟环境和代码
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app /app

# 环境变量
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1

# 暴露端口
EXPOSE 8000

# 运行(生产不用 --reload,开多 worker)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

多阶段构建的关键FROM python:3.12-slim AS builder 是构建阶段,装依赖;FROM python:3.12-slim 是运行阶段,只从构建阶段拷必要的东西(虚拟环境和代码)。运行镜像里没有 uv、没有构建工具、没有 .pyc 缓存——镜像更小、攻击面更小。

COPY pyproject.toml uv.lock ./COPY . . 前面——利用 Docker 层缓存:只改代码不改依赖时,依赖安装那层命中缓存,构建快很多。

二、前端 Dockerfile

前端构建分两步:Node 环境跑 npm run build 产出静态文件,Nginx 提供这些静态文件。

dockerfile
# frontend/Dockerfile

# ---- 构建阶段:Node 打包 ----
FROM node:20-slim AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci                    # npm ci 比 npm install 快,且严格按 lock 文件

COPY . .
RUN npm run build             # 产出 dist/ 目录

# ---- 运行阶段:Nginx 提供静态文件 ----
FROM nginx:alpine

# 从构建阶段拷打包产物到 Nginx 目录
COPY --from=builder /app/dist /usr/share/nginx/html

# Nginx 配置(见下文)
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

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

npm cinpm install 的区别:ci 严格按照 package-lock.json 安装,不改 lock 文件、不更新依赖——构建环境要求可重复,用 ci

nginx.conf 配置前端路由和 API 代理:

nginx
# frontend/nginx.conf
server {
    listen 80;
    server_name _;

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

    # Vue Router 的 history 模式:所有路径都返回 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 请求转发到后端
    location /api/ {
        proxy_pass http://backend:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

try_files $uri $uri/ /index.html 是 SPA(单页应用)的标配——Vue Router 用 history 模式时,用户直接访问 /assets 这个路径,服务器上没有这个文件,要返回 index.html 让 Vue Router 接管路由。

location /api//api/ 开头的请求转发到后端容器——生产环境前后端同域(都走 Nginx),没有跨域问题。开发时前端 5173 调后端 8000 有 CORS,生产时 Nginx 统一入口,CORS 自然消失。

三、docker-compose 编排

yaml
# docker-compose.yml
services:
  # 后端
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://appuser:s3cret@db:5432/opsconsole
      - SECRET_KEY=${SECRET_KEY}
      - DEBUG=false
    depends_on:
      - db
    restart: unless-stopped

  # 前端(Nginx)
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    restart: unless-stopped

  # 数据库
  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=appuser
      - POSTGRES_PASSWORD=s3cret
      - POSTGRES_DB=opsconsole
    volumes:
      - pgdata:/var/lib/postgresql/data   # 数据持久化
    restart: unless-stopped

volumes:
  pgdata:  # 数据库数据存在 Docker volume 里,容器删了数据还在

三个服务:backend(FastAPI)、frontend(Nginx + 前端静态文件)、db(PostgreSQL)。depends_on 保证启动顺序——后端等数据库、前端等后端。

volumes: pgdata 把数据库数据存到 Docker volume——容器删除重建数据不丢。不加 volume,docker-compose downup 数据库是空的。

backendfrontend 里的 http://backend:8000proxy_pass http://backend:8000——Docker Compose 网络里,服务名就是主机名。backend 服务监听 8000 端口,Nginx 用 http://backend:8000 就能访问。

四、构建和启动

bash
# 构建所有镜像并启动
docker-compose up -d --build

# 看日志
docker-compose logs -f backend

# 停止
docker-compose down

# 停止并删除数据(谨慎!数据库数据会丢)
docker-compose down -v

up -d --build 构建镜像并在后台启动。-d 是 detached(后台运行),--build 强制重新构建(改了代码后用)。

启动后访问 http://服务器IP——Nginx 提供前端页面,API 请求转发到后端,后端连数据库。三个容器跑在一个 Docker 网络里。