diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..21a90f8 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,70 @@ +name: Build and Push Docker Image + +on: + push: + branches: [main] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract version from package.json + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Detected package version: $VERSION" + + - name: Lowercase image name + id: image + run: | + IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "image_name=$IMAGE_NAME" >> "$GITHUB_OUTPUT" + + - name: Set up QEMU (for multi-arch builds) + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.image.outputs.image_name }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.version.outputs.version }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + target: production + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 9d2dfb1..46df692 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,83 @@ -# 多阶段构建 Dockerfile -FROM node:18-alpine AS base -WORKDIR /app -# 复制package文件 -COPY package*.json pnpm-lock.yaml ./ -# 安装pnpm -RUN npm install -g pnpm - -# 开发环境 -FROM base AS development -RUN pnpm install -COPY . . -EXPOSE 3000 -CMD ["pnpm", "dev", "--host", "0.0.0.0"] - -# 构建阶段 -FROM base AS builder -RUN pnpm install -COPY . . -RUN pnpm build - -# 生产环境 -FROM nginx:alpine AS production -# 复制自定义nginx配置 -COPY nginx.conf /etc/nginx/nginx.conf -# 复制构建产物 -COPY --from=builder /app/dist /usr/share/nginx/html -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +# 多阶段构建 Dockerfile +FROM node:18-alpine AS base +WORKDIR /app + +# 安装 pnpm +RUN npm install -g pnpm + +# ============================================ +# 前端构建阶段 +# ============================================ +FROM base AS frontend-builder +COPY package*.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile +COPY . . +RUN pnpm build + +# ============================================ +# 后端构建阶段 +# ============================================ +FROM base AS backend-builder +WORKDIR /app/server +COPY server/package*.json ./ +RUN pnpm install --frozen-lockfile --prod + +# ============================================ +# 生产环境 +# ============================================ +FROM node:18-alpine AS production +WORKDIR /app + +# 安装 tini 用于进程管理 +RUN apk add --no-cache tini + +# 复制后端依赖 +COPY --from=backend-builder /app/server/node_modules ./server/node_modules + +# 复制后端代码 +COPY server/package.json server/index.js ./server/ +COPY server/config ./server/config +COPY server/routes ./server/routes +COPY server/middleware ./server/middleware + +# 复制前端构建产物 +COPY --from=frontend-builder /app/dist ./dist + +# 创建数据目录 +RUN mkdir -p /app/data && chown -R node:node /app/data + +# 设置环境变量 +ENV NODE_ENV=production +ENV PORT=3000 +ENV DATA_DIR=/app/data + +# 暴露端口 +EXPOSE 3000 + +# 使用 tini 作为 PID 1 +ENTRYPOINT ["/sbin/tini", "--"] + +# 启动应用 +CMD ["node", "server/index.js"] + +# ============================================ +# 开发环境 +# ============================================ +FROM base AS development +WORKDIR /app + +# 复制所有 package 文件 +COPY package*.json pnpm-lock.yaml ./ +COPY server/package*.json ./server/ + +# 安装所有依赖 +RUN pnpm install + +# 复制源代码 +COPY . . + +# 暴露端口 +EXPOSE 3000 5173 + +# 启动开发服务器(前端) +CMD ["pnpm", "dev", "--host", "0.0.0.0"] diff --git a/docker-compose.yml b/docker-compose.yml index 6eb6b16..79207e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,41 +1,27 @@ -services: - # 开发环境 - app-dev: - build: - context: . - target: development - ports: - - "3000:3000" - volumes: - - .:/app - - /app/node_modules - environment: - - NODE_ENV=development - profiles: - - dev - - # 生产环境 - app-prod: - build: - context: . - target: production - ports: - - "80:80" - environment: - - NODE_ENV=production - restart: unless-stopped - profiles: - - prod - - # 使用外部nginx(可选) - nginx: - image: nginx:alpine - ports: - - "8080:80" - volumes: - - ./dist:/usr/share/nginx/html:ro - - ./nginx.conf:/etc/nginx/nginx.conf:ro - depends_on: - - app-prod - profiles: - - external-nginx \ No newline at end of file +version: '3.8' + +services: + mo-zhang: + build: + context: . + dockerfile: Dockerfile + target: production + container_name: mo-zhang + ports: + - "3000:3000" + volumes: + # 持久化数据库到本地 + - ./data:/app/data + environment: + - NODE_ENV=production + - PORT=3000 + - DATA_DIR=/app/data + - JWT_SECRET=${JWT_SECRET:-your-secret-key-change-in-production} + - TZ=Asia/Shanghai + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s diff --git a/index.html b/index.html index 53e6b5b..8c4fe16 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,16 @@
- + -