chore: productionize compose deployment

This commit is contained in:
Cheng Zhou
2026-03-27 16:13:28 +08:00
parent d8c5413b26
commit f2f856ea24
7 changed files with 182 additions and 79 deletions
+10 -1
View File
@@ -1,5 +1,14 @@
# CTMS 项目快速上手
## 生产部署
- 生产入口:`docker-compose.yaml`
- 启动方式:`docker compose up -d --build`
- 对外入口:Nginx 提供前端静态资源,并反代后端 API
- 验证方式:
- `docker compose config`
- `curl -i http://127.0.0.1/`
- `curl -i http://127.0.0.1/health`
## 账号与注册
- 初始化管理员:`admin@example.com / admin123`(已修正邮箱域名,后端启动时自动创建)
- 演示账号:PM `pm@example.com / pm123456`CRA `cra@example.com / cra123456`(仅用于体验,正式环境请修改密码或禁用)
@@ -16,7 +25,7 @@
- 普通成员(无项目角色):仅浏览
## 访问方式
- 前端:`http://localhost`Nginx 反代到 Vite 开发容器
- 前端:`http://localhost`Nginx 直接托管前端静态文件
- 后端 API:同域 `/api/v1/*`(已在前端代理)
## 常用流程
+4 -62
View File
@@ -1,9 +1,4 @@
services:
# =========================================
# [主演] 业务服务 (日常开发一直开着)
# =========================================
# 1. 数据库 (PostgreSQL)
db:
image: postgres:15-alpine
container_name: ctms_db
@@ -27,18 +22,14 @@ services:
networks:
- ctms_net
# 2. 后端 (FastAPI)
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: ctms_backend
command: python -m debugpy --listen 0.0.0.0:5678 -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --proxy-headers --forwarded-allow-ips '*'
volumes:
- ./backend:/code
restart: always
ports:
- "8000:8000"
- "5678:5678"
environment:
DATABASE_URL: postgresql+asyncpg://ctms_user:secret_password@db/ctms_db
depends_on:
@@ -47,56 +38,10 @@ services:
networks:
- ctms_net
# 3. 前端 (Vue3)
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: ctms_frontend
restart: always
# 开启 Host 模式以支持 Vite 热更新
command: npm run dev -- --host
volumes:
- ./frontend:/app
# [关键] 让容器使用自己的 node_modules,而不是宿主机的
- /app/node_modules
ports:
- "5173:5173"
environment:
VITE_API_BASE_URL: /
NODE_OPTIONS: --max-old-space-size=4096
depends_on:
- backend
networks:
- ctms_net
# =========================================
# [剧务] 初始化工具 (只在需要时召唤)
# profiles: ["init"] 标记了它们平时不会启动
# =========================================
# Node 工具箱:用于生成 Vue 代码、安装 npm 包
frontend-init:
image: node:latest
profiles: ["init"]
working_dir: /app
volumes:
- ./frontend:/app
networks:
- ctms_net
# Python 工具箱:用于生成 requirements.txt 等
backend-init:
image: python:3.10-slim
profiles: ["init"]
working_dir: /code
volumes:
- ./backend:/code
networks:
- ctms_net
nginx:
image: nginx:latest
build:
context: .
dockerfile: nginx/Dockerfile
container_name: ctms_nginx
restart: always
ports:
@@ -104,16 +49,13 @@ services:
- "443:443" # HTTPS
- "8888:8888"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
- ./frontend/public/favicon.ico:/usr/share/nginx/html/favicon.ico:ro
depends_on:
- backend
- frontend
networks:
- ctms_net
networks:
ctms_net:
driver: bridge
@@ -0,0 +1,27 @@
# Production Compose Design
**Goal:** Convert the current single-host `docker-compose` deployment from a development-oriented stack into a production-oriented stack while keeping the existing `docker-compose.yaml` as the only deployment entrypoint.
**Decisions**
- Keep single-host `docker-compose` deployment.
- Convert the frontend to a static build served directly by Nginx.
- Keep PostgreSQL and the existing `pg_data` persistence model.
- Keep one backend container and remove development-only runtime behavior.
- Update the existing `docker-compose.yaml` in place instead of introducing a separate production file.
**Architecture**
- `db` remains a long-lived PostgreSQL service with the existing bind-mounted data directory.
- `backend` runs from the backend image default command and exposes only the application port needed inside the compose network.
- `nginx` becomes the public entrypoint. It serves the built frontend assets and proxies `/api/` and `/health` to the backend service.
- The frontend is no longer a long-running service in production. Its build artifacts are produced during the image build and copied into the Nginx image.
**Cleanup Scope**
- Remove development-only compose services such as toolbox/init containers.
- Remove development-only backend startup overrides such as `debugpy` and `uvicorn --reload`.
- Remove development-only frontend runtime behavior such as `npm run dev`, bind mounts, and exposed Vite ports.
- Keep data persistence and core service names stable where practical to limit operational change.
**Operational Notes**
- Updating `docker-compose.yaml` in place means local development will no longer use the previous hot-reload setup by default.
- Frontend SPA routing must be handled by Nginx with `try_files`.
- Verification is done through `docker compose config`, image builds, container startup, and HTTP health checks.
@@ -0,0 +1,110 @@
# Production Compose Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Convert the repository's only `docker-compose.yaml` from development deployment to production deployment using static frontend assets served by Nginx.
**Architecture:** The backend runs as a production FastAPI container, the frontend is built into static assets during image build, and Nginx serves the SPA while reverse proxying backend routes. PostgreSQL remains persistent through the existing `pg_data` bind mount.
**Tech Stack:** Docker Compose, FastAPI, Vue/Vite, Nginx, PostgreSQL
---
### Task 1: Productionize Frontend Image
**Files:**
- Modify: `frontend/Dockerfile`
**Step 1: Replace dev runtime with a multi-stage production build**
Update the Dockerfile so a Node build stage runs dependency install and `npm run build`, then a lightweight stage exposes the built `dist` directory for downstream use instead of running `npm run dev`.
**Step 2: Keep the output suitable for Nginx consumption**
Ensure the final stage stores frontend assets in a predictable directory such as `/usr/share/nginx/html`.
**Step 3: Verify the image definition is syntactically valid**
Run: `docker compose config`
Expected: compose renders successfully with no Dockerfile-related errors.
### Task 2: Productionize Nginx Runtime
**Files:**
- Modify: `nginx/nginx.conf`
**Step 1: Add static frontend serving**
Set the web root to the built frontend assets and add `try_files $uri $uri/ /index.html;` for SPA routing.
**Step 2: Keep backend reverse proxy behavior**
Preserve `/api/` and `/health` proxying to `backend:8000` with standard forwarded headers.
**Step 3: Verify config structure through compose-backed startup**
Run: `docker compose config`
Expected: compose renders successfully.
### Task 3: Productionize Compose Topology
**Files:**
- Modify: `docker-compose.yaml`
**Step 1: Remove dev-only services and settings**
Delete `frontend`, `frontend-init`, and `backend-init`, and remove bind mounts, debug ports, and development commands that are only useful for hot reload or scaffolding.
**Step 2: Wire backend and nginx for production**
Allow the backend to use its Dockerfile default command, keep the database dependency, and expose only the ports needed for the production stack.
**Step 3: Build frontend assets into nginx**
Point the Nginx service build to a production image definition that includes the built frontend output.
**Step 4: Verify rendered compose**
Run: `docker compose config`
Expected: only the production services remain and configuration renders cleanly.
### Task 4: Add Deployment Verification
**Files:**
- Modify: `README.md`
**Step 1: Update deployment entry description**
Document that `docker-compose.yaml` is now the production deployment entrypoint.
**Step 2: Add minimal verification commands**
Document `docker compose up -d --build`, plus health checks for `/` and `/health`.
**Step 3: Verify behavior**
Run: `docker compose up -d --build`
Expected: `db`, `backend`, and `nginx` start successfully.
### Task 5: End-to-End Verification
**Files:**
- Verify only
**Step 1: Render final compose**
Run: `docker compose config`
Expected: PASS
**Step 2: Start stack**
Run: `docker compose up -d --build`
Expected: PASS
**Step 3: Check HTTP endpoints**
Run: `curl -i http://127.0.0.1/`
Expected: `200 OK` and HTML payload
Run: `curl -i http://127.0.0.1/health`
Expected: successful backend health response
+12 -4
View File
@@ -1,7 +1,15 @@
FROM node:20-alpine
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm ci
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "--port", "5173"]
RUN npm run build
FROM alpine:3.21
WORKDIR /opt/frontend
COPY --from=build /app/dist ./dist
+14
View File
@@ -0,0 +1,14 @@
FROM node:20-alpine AS frontend-build
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
FROM nginx:1.27-alpine
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=frontend-build /app/dist /usr/share/nginx/html
+3 -10
View File
@@ -20,11 +20,8 @@ http {
sendfile on;
keepalive_timeout 65;
upstream frontend {
zone frontend 64k;
server frontend:5173 resolve;
}
root /usr/share/nginx/html;
index index.html;
upstream backend {
zone backend 64k;
@@ -62,11 +59,7 @@ http {
}
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
try_files $uri $uri/ /index.html;
}
}
}