From 5410a0b8abb51f71dfd6fc395aadb4f750459f94 Mon Sep 17 00:00:00 2001 From: Lxy Date: Tue, 3 Mar 2026 00:19:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0docker=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E9=83=A8=E7=BD=B2=E4=B8=80=E9=94=AE=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/backend/Dockerfile | 45 ++++++++++------ app/backend/docker-compose.yml | 95 ++++++++++++++++++++++------------ 2 files changed, 92 insertions(+), 48 deletions(-) diff --git a/app/backend/Dockerfile b/app/backend/Dockerfile index 375e87e..55863d9 100644 --- a/app/backend/Dockerfile +++ b/app/backend/Dockerfile @@ -1,54 +1,69 @@ -# 构建阶段 +# ============================================ +# A股智投分析平台 - 后端服务 Dockerfile +# 多阶段构建,生产环境优化 +# ============================================ + +# 阶段1:构建阶段 FROM node:20-alpine AS builder WORKDIR /app +# 安装必要依赖 +RUN apk add --no-cache openssl + # 复制 package.json 和 package-lock.json COPY package*.json ./ COPY prisma ./prisma/ -# 安装依赖 +# 安装所有依赖(包括开发依赖) RUN npm ci -# 复制源代码 -COPY . . - # 生成 Prisma Client RUN npx prisma generate -# 构建 TypeScript +# 复制源代码 +COPY . . + +# 编译 TypeScript RUN npm run build -# 生产阶段 +# ============================================ +# 阶段2:生产阶段 FROM node:20-alpine WORKDIR /app # 安装必要的系统依赖 -RUN apk add --no-cache openssl +RUN apk add --no-cache openssl wget + +# 创建日志目录 +RUN mkdir -p logs # 复制 package.json 和 package-lock.json COPY package*.json ./ COPY prisma ./prisma/ -# 安装生产依赖 -RUN npm ci --only=production +# 安装生产依赖(不包含开发依赖) +RUN npm ci --only=production && npm cache clean --force -# 生成 Prisma Client(针对生产环境) +# 重新生成 Prisma Client(针对生产环境) RUN npx prisma generate # 从构建阶段复制编译后的代码 COPY --from=builder /app/dist ./dist -# 创建日志目录 -RUN mkdir -p logs +# 创建非 root 用户 +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nodejs -u 1001 +RUN chown -R nodejs:nodejs /app/logs +USER nodejs # 暴露端口 EXPOSE 3000 # 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1 +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1 # 启动命令 CMD ["node", "dist/app.js"] diff --git a/app/backend/docker-compose.yml b/app/backend/docker-compose.yml index 638bfda..b1fb8fe 100644 --- a/app/backend/docker-compose.yml +++ b/app/backend/docker-compose.yml @@ -1,56 +1,43 @@ version: '3.8' services: - # 后端应用 - app: - build: . - ports: - - "3000:3000" - environment: - - NODE_ENV=production - - PORT=3000 - - DATABASE_URL=mysql://root:rootpass@mysql:3306/aguzhitou - - REDIS_URL=redis://redis:6379 - - JWT_SECRET=${JWT_SECRET:-your-secret-key-min-32-characters-long} - - JWT_EXPIRES_IN=7d - - LOG_LEVEL=info - - AKSHARE_URL=http://akshare:8000 - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy - volumes: - - ./logs:/app/logs - restart: always - networks: - - aguzhitou-network - - # MySQL 数据库 + # MySQL 8.0 数据库 mysql: image: mysql:8.0 + container_name: aguzhitou-mysql environment: - - MYSQL_ROOT_PASSWORD=rootpass - - MYSQL_DATABASE=aguzhitou - - MYSQL_CHARSET=utf8mb4 - - MYSQL_COLLATION=utf8mb4_unicode_ci + MYSQL_ROOT_PASSWORD: 1qazse42W3 + MYSQL_DATABASE: aguzhitou + MYSQL_CHARSET: utf8mb4 + MYSQL_COLLATION: utf8mb4_unicode_ci + TZ: Asia/Shanghai volumes: - mysql_data:/var/lib/mysql + - ./init-scripts:/docker-entrypoint-initdb.d:ro ports: - "3306:3306" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"] + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p1qazse42W3"] interval: 10s timeout: 5s retries: 5 + start_period: 30s restart: always networks: - aguzhitou-network - command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + command: > + --default-authentication-plugin=mysql_native_password + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + --innodb_buffer_pool_size=512M + --max_connections=200 + --wait_timeout=28800 + --interactive_timeout=28800 - # Redis 缓存 + # Redis 7 缓存 redis: image: redis:7-alpine + container_name: aguzhitou-redis volumes: - redis_data:/data ports: @@ -64,19 +51,61 @@ services: networks: - aguzhitou-network + # 后端应用 + app: + build: + context: . + dockerfile: Dockerfile + container_name: aguzhitou-app + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_URL: mysql://root:1qazse42W3@mysql:3306/aguzhitou + REDIS_URL: redis://redis:6379 + JWT_SECRET: aguzhitou-docker-secret-key-2024-prod-only-min-32-characters + JWT_EXPIRES_IN: 7d + LOG_LEVEL: info + AKSHARE_URL: http://localhost:8000 + ports: + - "3000:3000" + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ./logs:/app/logs + restart: always + networks: + - aguzhitou-network + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # AKShare 数据服务(可选) akshare: image: registry.cn-shanghai.aliyuncs.com/akshare/akshare:latest + container_name: aguzhitou-akshare ports: - "8000:8000" restart: always networks: - aguzhitou-network + profiles: + - with-akshare volumes: mysql_data: + driver: local redis_data: + driver: local networks: aguzhitou-network: driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16