From 89ee10a8a8466f21a822d608fe5fcb2a1b24679e Mon Sep 17 00:00:00 2001 From: GaoNeng-wWw Date: Sat, 25 Jul 2026 15:55:39 +0800 Subject: [PATCH] feat: docker deploy example --- template/docker-compose.yaml | 72 ++++++++++++++++++++++ template/nest-next/bash/migrator.sh | 2 + template/nest-next/bash/run.sh | 2 + template/nest-next/bash/seeder.sh | 2 + template/nest-next/dockerfile | 12 ++-- template/nest-next/src/main.ts | 13 ++-- template/tinyvue/config/vite.config.dev.ts | 14 ++--- template/tinyvue/dockerfile | 26 ++++++++ template/tinyvue/nginx.conf.template | 36 +++++++++++ 9 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 template/docker-compose.yaml create mode 100644 template/tinyvue/dockerfile create mode 100644 template/tinyvue/nginx.conf.template diff --git a/template/docker-compose.yaml b/template/docker-compose.yaml new file mode 100644 index 00000000..c7411a26 --- /dev/null +++ b/template/docker-compose.yaml @@ -0,0 +1,72 @@ +services: + mysql: + image: mysql:8 + restart: always + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: demo_tiny_pro + ports: + - "3306:3306" + command: + - --mysql-native-password=ON + redis: + image: redis + ports: + - "6379:6379" + db_deploy: + build: + context: ./nest-next + dockerfile: dockerfile + target: migrator + environment: + DATABASE_HOST: mysql + DATABASE_PORT: 3306 + DATABASE_USERNAME: root + DATABASE_PASSWORD: root + DATABASE_NAME: demo_tiny_pro + depends_on: + - mysql + seeder: + build: + context: ./nest-next + dockerfile: dockerfile + target: seeder + environment: + DATABASE_HOST: mysql + DATABASE_PORT: 3306 + DATABASE_USERNAME: root + DATABASE_PASSWORD: root + DATABASE_NAME: demo_tiny_pro + depends_on: + db_deploy: + condition: service_completed_successfully + srv: + build: + context: ./nest-next + dockerfile: dockerfile + target: final + environment: + DATABASE_HOST: mysql + DATABASE_PORT: 3306 + DATABASE_USERNAME: root + DATABASE_PASSWORD: root + DATABASE_NAME: demo_tiny_pro + ports: + - "3000:3000" + depends_on: + seeder: + condition: service_completed_successfully + volumes: + - ./nest-next/configs:/app/dist/config + web: + build: + context: ./tinyvue + dockerfile: dockerfile + target: final + ports: + - "80:80" + environment: + BACKEND_HOST: http://srv:3000 + depends_on: + srv: + condition: service_started diff --git a/template/nest-next/bash/migrator.sh b/template/nest-next/bash/migrator.sh index 33c8e7d6..264aaa63 100644 --- a/template/nest-next/bash/migrator.sh +++ b/template/nest-next/bash/migrator.sh @@ -1,3 +1,5 @@ #!/bin/bash +set -e + wait4x mysql "${DATABASE_USERNAME}:${DATABASE_PASSWORD}@tcp(${DATABASE_HOST}:${DATABASE_PORT})/${DATABASE_NAME}" --timeout=200s --interval=2s -- pnpm run migrate:run diff --git a/template/nest-next/bash/run.sh b/template/nest-next/bash/run.sh index aaa4671d..173df08d 100644 --- a/template/nest-next/bash/run.sh +++ b/template/nest-next/bash/run.sh @@ -1,3 +1,5 @@ #!/bin/bash +set -e + wait4x mysql "${DATABASE_USERNAME}:${DATABASE_PASSWORD}@tcp(${DATABASE_HOST}:${DATABASE_PORT})/${DATABASE_NAME}" --timeout=200s --interval=2s -- node ./dist/main.js diff --git a/template/nest-next/bash/seeder.sh b/template/nest-next/bash/seeder.sh index ec475f70..ba069e3d 100644 --- a/template/nest-next/bash/seeder.sh +++ b/template/nest-next/bash/seeder.sh @@ -1,3 +1,5 @@ #!/bin/bash +set -e + wait4x mysql "${DATABASE_USERNAME}:${DATABASE_PASSWORD}@tcp(${DATABASE_HOST}:${DATABASE_PORT})/${DATABASE_NAME}" --timeout=200s --interval=2s -- pnpm run seeder:run diff --git a/template/nest-next/dockerfile b/template/nest-next/dockerfile index 0db01f01..58137e42 100644 --- a/template/nest-next/dockerfile +++ b/template/nest-next/dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine AS base +FROM node:22-alpine AS api_base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME/bin:$PATH" RUN npm config set registry https://registry.npmmirror.com && \ @@ -9,14 +9,14 @@ RUN npm config set registry https://registry.npmmirror.com && \ COPY . /app WORKDIR /app -FROM base as prod-deps +FROM api_base as prod-deps RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -FROM base as builder +FROM api_base as builder RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install RUN pnpm run build -FROM base as migrator +FROM api_base as migrator ENV DATABASE_HOST="" ENV DATABASE_PORT="" @@ -29,7 +29,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm add @oxc-node/core CMD ["sh", "./bash/migrator.sh"] -FROM base as seeder +FROM api_base as seeder RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm add @oxc-node/core @@ -42,7 +42,7 @@ ENV DATABASE_NAME="" CMD ["sh", "./bash/seeder.sh"] -FROM base as final +FROM api_base as final COPY --from=prod-deps /app/node_modules /app/node_modules COPY --from=builder /app/dist /app/dist diff --git a/template/nest-next/src/main.ts b/template/nest-next/src/main.ts index c95b8390..fba6eba1 100644 --- a/template/nest-next/src/main.ts +++ b/template/nest-next/src/main.ts @@ -1,21 +1,26 @@ import * as winston from 'winston'; import 'winston-daily-rotate-file'; import { utilities, WinstonModule } from 'nest-winston'; - import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { INestApplication, VersioningType } from '@nestjs/common'; +import { + INestApplication, + RequestMethod, + VersioningType, +} from '@nestjs/common'; import { ConfigureService, SwaggerConfigure } from '@app/configure'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { I18nValidationPipe } from 'nestjs-i18n'; async function bootstrap() { const app = await NestFactory.create(AppModule, { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access logger: WinstonModule.createLogger({ instance: setupLogger(), }), }); + app.setGlobalPrefix('/api', { + exclude: [{ path: 'healthCheck', method: RequestMethod.GET }], + }); app.enableVersioning({ type: VersioningType.MEDIA_TYPE, key: 'v=', @@ -57,7 +62,7 @@ function setupLogger() { // 字符串拼接 format: winston.format.combine( winston.format.timestamp(), - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + utilities.format.nestLike(), ), }), diff --git a/template/tinyvue/config/vite.config.dev.ts b/template/tinyvue/config/vite.config.dev.ts index 9e47f0db..e02cf0d3 100644 --- a/template/tinyvue/config/vite.config.dev.ts +++ b/template/tinyvue/config/vite.config.dev.ts @@ -11,23 +11,23 @@ configDotenv({ // 加载环境变量(development 模式会读取 .env.development 和 .env) const env = loadEnv('development', process.cwd()) - const proxyConfig = { [env.VITE_BASE_API]: { target: env.VITE_SERVER_HOST, changeOrigin: true, logLevel: 'debug', - rewrite: (path: string) => - path.replace( - new RegExp(`${env.VITE_BASE_API}`), - '', - ), + rewrite: (path: string) => { + if (path.startsWith('/api/mock')) { + return path.replace('/api/mock/api', '/api/mock'); + } + return path; + } }, [env.VITE_MOCK_SERVER_HOST]: { target: env.VITE_SERVER_HOST, changeOrigin: true, rewrite: (path: string) => { - return path.replace(new RegExp(`${env.VITE_MOCK_SERVER_HOST}`), '/mock') + return path.replace(new RegExp(`${env.VITE_MOCK_SERVER_HOST}/api`), '/mock') }, }, } diff --git a/template/tinyvue/dockerfile b/template/tinyvue/dockerfile new file mode 100644 index 00000000..454d6743 --- /dev/null +++ b/template/tinyvue/dockerfile @@ -0,0 +1,26 @@ +FROM node:22-alpine as base + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME/bin:$PATH" +RUN npm config set registry https://registry.npmmirror.com && \ + npm i -g pnpm@10.8.0 + +COPY . /app +WORKDIR /app + +FROM base as prod-deps +RUN pnpm uninstall @opentiny/vue-theme && \ + pnpm add @opentiny/vue-theme@3 && \ + pnpm install + +FROM prod-deps as builder +RUN pnpm run build + +FROM nginx:alpine as final + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf.template /etc/nginx/conf.d/default.conf.template + +ENV BACKEND_HOST = ""; + +CMD ["/bin/sh", "-c", "envsubst '${BACKEND_HOST}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"] diff --git a/template/tinyvue/nginx.conf.template b/template/tinyvue/nginx.conf.template new file mode 100644 index 00000000..39a45bc4 --- /dev/null +++ b/template/tinyvue/nginx.conf.template @@ -0,0 +1,36 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + + location /api/ { + + if ($uri ~ ^/api/mock) { + rewrite ^/api/mock/api(/.*)?$ /api/mock$1 break; + } + + proxy_pass ${BACKEND_HOST}; + + 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; + + access_log /var/log/nginx/api_access.log; + error_log /var/log/nginx/api_error.log debug; + } + + + location / { + try_files $uri $uri/ /index.html; + } + + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +}