Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -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
112 changes: 83 additions & 29 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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;"]
# 多阶段构建 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"]
68 changes: 27 additions & 41 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
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
10 changes: 7 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>91写作 - AI智能小说创作工具</title>
<meta name="description" content="墨章 - 基于 AI 的智能小说创作工具" />
<title>墨章 · AI 智能小说创作工具</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700&family=Cormorant+Garamond:wght@500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
</html>
Loading