diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..97b8ab8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,62 @@ +# 依赖 +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# 构建产物 +dist +dist-ssr +*.local + +# 编辑器 +.vscode +.idea +*.swp +*.swo +*~ + +# 操作系统 +.DS_Store +Thumbs.db + +# Git +.git +.gitignore +.gitattributes + +# CI/CD +.gitea +.github +.gitlab-ci.yml + +# Docker +Dockerfile +.dockerignore +docker-compose.yaml +docker-compose.*.yaml +compose*yaml + +# 测试 +coverage +.nyc_output + +# 环境变量 +.env +.env.local +.env.*.local + +# 日志 +logs +*.log + +# 临时文件 +tmp +temp + +# 文档 +README.md +LICENSE +*.md + diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..4753407 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build and Push Docker Image + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置 Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: 登录到 Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GITEA_INSTANCE_URL }} + username: ${{ env.GITEA_ACTOR }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 提取 Docker 元数据 + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GITEA_INSTANCE_URL }}/${{ env.GITEA_REPOSITORY_OWNER }}/litek + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest + + - name: 构建并推送 Docker 镜像 + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ed81e65 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# 第一阶段: 构建应用 +FROM node:22-alpine AS builder + +# 安装 pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# 设置工作目录 +WORKDIR /app + +# 复制依赖配置文件 +COPY package.json pnpm-lock.yaml ./ + +# 安装依赖 +RUN pnpm install --frozen-lockfile + +# 复制源代码 +COPY . . + +# 构建应用 +RUN pnpm build + +# 第二阶段: 运行应用 +FROM nginx:alpine + +# 复制自定义 nginx 配置 +COPY nginx.conf /etc/nginx/nginx.conf + +# 从构建阶段复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 暴露端口 +EXPOSE 80 + +# 启动 nginx +CMD ["nginx", "-g", "daemon off;"] + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..8ca70b4 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,73 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip 压缩配置 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml font/truetype font/opentype + application/vnd.ms-fontobject image/svg+xml; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA 路由支持 - 所有请求都返回 index.html + location / { + try_files $uri $uri/ /index.html; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 错误页面 + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} +