部署运维

Docker 部署

使用 Docker 可以避免 better-sqlite3 的本地编译问题,并提供更一致的运行环境。本章提供 Dockerfile 和 docker-compose 配置参考。

Docker 部署是解决 better-sqlite3 编译失败的最简单方案,特别适合 Windows/macOS 开发环境或不想手动配置编译工具链的场景。

后端 Dockerfile

backend/ 目录创建 Dockerfile

# backend/Dockerfile
FROM node:20-bullseye-slim

# 安装编译工具(better-sqlite3 需要)
RUN apt-get update && apt-get install -y \
    build-essential \
    python3 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 先复制 package.json,利用 Docker 层缓存
COPY package*.json ./
RUN npm install

# 复制源码并编译
COPY . .
RUN npm run build

# 创建数据目录
RUN mkdir -p /app/data /app/uploads

EXPOSE 3001

CMD ["node", "dist/server.js"]

前端 Dockerfile

frontend/ 目录创建 Dockerfile

# frontend/Dockerfile
# 构建阶段
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

# 生产阶段:使用 Nginx 提供静态文件
FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html

# Nginx SPA 路由配置
RUN echo 'server { \
    listen 80; \
    root /usr/share/nginx/html; \
    index index.html; \
    location / { try_files $uri $uri/ /index.html; } \
    location /api/ { proxy_pass http://backend:3001; } \
}' > /etc/nginx/conf.d/default.conf

EXPOSE 80

docker-compose.yml

在项目根目录创建 docker-compose.yml

version: '3.8'

services:
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: edubuddy-backend
    restart: unless-stopped
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - PORT=3001
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}
      - OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o}
      - JWT_SECRET=${JWT_SECRET:-change-me-in-production}
      - DB_PATH=/app/data/edubuddy.db
      - UPLOAD_DIR=/app/uploads
    volumes:
      # 持久化数据库和上传文件
      - edubuddy-data:/app/data
      - edubuddy-uploads:/app/uploads
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: edubuddy-frontend
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      backend:
        condition: service_healthy

volumes:
  edubuddy-data:
    driver: local
  edubuddy-uploads:
    driver: local

环境变量文件

在项目根目录创建 .env(docker-compose 会自动读取):

OPENAI_API_KEY=sk-your-api-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o
JWT_SECRET=your-very-long-random-secret-string

启动命令

# 首次构建并启动
docker-compose up -d --build

# 查看运行状态
docker-compose ps

# 查看后端日志
docker-compose logs -f backend

# 停止服务
docker-compose down

# 停止并删除数据卷(危险!会删除数据库)
docker-compose down -v

更新部署

# 拉取最新代码
git pull origin main

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

# 只重建特定服务
docker-compose up -d --build backend

数据库备份(Docker 环境)

# 从容器内复制数据库文件到宿主机
docker cp edubuddy-backend:/app/data/edubuddy.db ./backup-$(date +%Y%m%d).db

# 或使用 docker exec 执行 SQLite 在线备份
docker exec edubuddy-backend sqlite3 /app/data/edubuddy.db \
  ".backup /app/data/backup.db"
docker cp edubuddy-backend:/app/data/backup.db ./backup-$(date +%Y%m%d).db

常见 Docker 问题

better-sqlite3 编译错误

确保 Dockerfile 中包含 build-essentialpython3,并使用 node:20-bullseye-slim 而非 Alpine(Alpine 编译环境较特殊)。

前端无法访问后端 API

确认 Nginx 配置中的 proxy_pass 指向 http://backend:3001(Docker 内部服务名),而非 localhost:3001

数据卷权限问题

# 如果后端启动时报权限错误
docker exec -it edubuddy-backend sh
chown -R node:node /app/data /app/uploads