Files
opencode/skill/gitea/dockerfile-templates/nodejs-frontend.md
voson 425ca5b5fd feat(gitea): 添加 Dockerfile 模板和 Rust 支持,优化 runner 网络配置说明
- 新增 Go、Node.js、Rust 服务的 Dockerfile 模板
- 新增 Rust 快速参考指南
- 新增 Rust 后端工作流模板
- 优化 create-runner.md,明确 host 网络模式为缓存必需条件
- 更新 gitea skill 主文档
2026-01-30 10:12:09 +08:00

14 KiB
Raw Permalink Blame History

Node.js 前端 Dockerfile 模板

Node.js 前端项目的 Docker 容器化模板,使用 Nginx 提供静态文件服务。

模板类型

1. CI 构建镜像Dockerfile.ci

适用场景:在 CI/CD 中使用,静态文件已在外部构建完成。

优点

  • 镜像体积最小(~20MB
  • 构建速度快
  • 适合生产环境
# CI 构建专用 Dockerfile
# 将不常变化的层放在前面,最大化利用缓存
# 使用固定版本避免 latest 导致的缓存失效和不确定性

FROM nginx:1.28.0-alpine

# 基础设置层 - 很少变化,可以长期缓存
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk add --no-cache tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# Nginx 配置文件 - 配置 SPA 路由处理
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 应用静态文件 - 每次构建都变化,放在最后
COPY dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

使用说明

  1. 创建 nginx.conf 配置文件
  2. 在 CI workflow 中先构建前端(生成 dist 目录),再构建镜像
  3. 根据实际需求修改 Nginx 配置

2. 完整构建镜像Dockerfile

适用场景:本地开发、无 CI 环境、独立构建。

优点

  • 自包含构建流程
  • 可在任何环境构建
  • 利用 Docker 缓存加速
# ============================================
# 构建阶段
# ============================================
FROM node:22-alpine AS builder

# 安装构建依赖
RUN apk add --no-cache git

WORKDIR /build

# 设置 npm 镜像源(国内加速)
RUN npm config set registry https://registry.npmmirror.com

# 安装 pnpm
RUN npm install -g pnpm@latest-10

# 先复制依赖文件,利用 Docker 层缓存
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# 复制源码
COPY . .

# 构建应用
# 根据项目使用的构建工具修改命令
RUN pnpm run build

# ============================================
# 运行阶段
# ============================================
FROM nginx:1.28.0-alpine

# 安装运行时依赖
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk add --no-cache tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 从构建阶段复制静态文件
COPY --from=builder /build/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

使用说明

  1. 根据项目使用 npm、yarn 或 pnpm 修改安装命令
  2. 根据构建工具Vite、Webpack、Next.js 等)修改构建命令和输出目录
  3. 调整 Nginx 配置

3. 多环境构建版Dockerfile.multienv

适用场景需要根据不同环境dev/test/prod构建不同配置。

# ============================================
# 构建阶段
# ============================================
FROM node:22-alpine AS builder

ARG BUILD_ENV=production

WORKDIR /build

RUN npm config set registry https://registry.npmmirror.com && \
    npm install -g pnpm@latest-10

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .

# 根据 BUILD_ENV 设置不同的环境变量
ENV NODE_ENV=${BUILD_ENV}
ENV VITE_APP_ENV=${BUILD_ENV}

RUN pnpm run build

# ============================================
# 运行阶段
# ============================================
FROM nginx:1.28.0-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk add --no-cache tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /build/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

构建命令

# 生产环境
docker build --build-arg BUILD_ENV=production -t app:prod .

# 测试环境
docker build --build-arg BUILD_ENV=test -t app:test .

# 开发环境
docker build --build-arg BUILD_ENV=development -t app:dev .

Nginx 配置模板

nginx.conf基础 SPA 配置)

server {
    listen 80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    # 启用 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript 
               application/x-javascript application/xml+rss 
               application/json application/javascript;

    # 静态资源缓存
    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;
    }
}

nginx.conf带 API 代理)

server {
    listen 80;
    server_name _;

    client_max_body_size 30M;

    root /usr/share/nginx/html;
    index index.html;

    # 启用 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript 
               application/x-javascript application/xml+rss 
               application/json application/javascript;

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # SPA 路由处理
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 代理(根据实际后端服务修改)
    location /api/ {
        proxy_pass http://backend-service:8080/;
        proxy_http_version 1.1;
        proxy_read_timeout 300s;
        proxy_redirect off;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

nginx.conf完整生产配置

# SPA 应用 Nginx 配置
# 所有路由都返回 index.html由前端路由处理

server {
    listen 80;
    server_name _;

    client_max_body_size 30M;

    root /usr/share/nginx/html;
    index index.html;

    # 启用 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript 
               application/x-javascript application/xml+rss 
               application/json application/javascript;

    # 静态资源缓存(带版本号的文件可以长期缓存)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # HTML 文件不缓存(确保用户总是获取最新版本)
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }

    # SPA 路由处理:所有请求都返回 index.html
    location / {
        # 用于配合 browserHistory 使用
        try_files $uri $uri/ /index.html;
    }

    # API 代理:平台 API
    location /platform/api/ {
        proxy_pass http://backend-service:2020/;
        proxy_http_version 1.1;
        proxy_read_timeout 300s;
        proxy_redirect off;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # API 代理:微信小程序 API
    location /wxmp/api/ {
        proxy_pass http://backend-service:2021/;
        proxy_http_version 1.1;
        proxy_read_timeout 300s;
        proxy_redirect off;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 第三方服务代理(如高德地图)
    location /_AMapService/ {
        set $args "$args&jscode=YOUR_AMAP_KEY";
        proxy_pass https://restapi.amap.com/;
    }

    # 错误页面
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

高级配置

支持 HTTPS

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /usr/share/nginx/html;
    index index.html;

    # ... 其他配置
}

防盗链

location ~* \.(jpg|jpeg|png|gif|svg)$ {
    valid_referers none blocked example.com *.example.com;
    if ($invalid_referer) {
        return 403;
    }
    expires 1y;
}

限流

# 在 http 块中定义限流区域
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

# 在 server 块中应用
location / {
    limit_req zone=one burst=20 nodelay;
    try_files $uri $uri/ /index.html;
}

安全头

location / {
    # 防止点击劫持
    add_header X-Frame-Options "SAMEORIGIN" always;
    
    # 防止 MIME 类型嗅探
    add_header X-Content-Type-Options "nosniff" always;
    
    # 启用 XSS 保护
    add_header X-XSS-Protection "1; mode=block" always;
    
    # CSP 策略
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
    
    try_files $uri $uri/ /index.html;
}

镜像优化技巧

1. 使用多阶段构建

# 分离构建和运行环境,只保留必要文件
FROM node:22-alpine AS builder
# ... 构建步骤

FROM nginx:1.28.0-alpine
# 只复制构建产物
COPY --from=builder /build/dist /usr/share/nginx/html

2. 利用构建缓存

# 先复制依赖文件
COPY package.json pnpm-lock.yaml ./
RUN pnpm install

# 再复制源码(源码变化频繁)
COPY . .

3. 使用 .dockerignore

# .dockerignore
node_modules/
.git/
.gitignore
*.md
docs/
tests/
.github/
.gitea/
Dockerfile*
docker-compose.yml
.env*
dist/
build/
coverage/
.vscode/
.idea/

4. 优化 Nginx 配置

# 启用 sendfile
sendfile on;

# 启用 tcp_nopush
tcp_nopush on;

# 启用 tcp_nodelay
tcp_nodelay on;

# 设置 keepalive 超时
keepalive_timeout 65;

# 禁用访问日志(生产环境可选)
access_log off;

构建命令示例

本地构建

# 基础构建
docker build -t frontend:latest .

# 指定 Dockerfile
docker build -f Dockerfile.ci -t frontend:latest .

# 传递构建参数
docker build --build-arg BUILD_ENV=production -t frontend:1.0.0 .

CI/CD 构建

# 使用 BuildKit 缓存
export DOCKER_BUILDKIT=1

# 使用注册表缓存
docker build \
  --cache-from=type=registry,ref=frontend:buildcache \
  --cache-to=type=registry,ref=frontend:buildcache,mode=max \
  -t frontend:latest \
  .

运行容器示例

# 基础运行
docker run -d -p 80:80 frontend:latest

# 挂载自定义 Nginx 配置
docker run -d \
  -p 80:80 \
  -v ./nginx.conf:/etc/nginx/conf.d/default.conf \
  frontend:latest

# 设置环境变量(如果 Nginx 配置需要)
docker run -d \
  -p 80:80 \
  -e BACKEND_HOST=api.example.com \
  frontend:latest

# 查看日志
docker logs -f <container-id>

# 进入容器调试
docker exec -it <container-id> /bin/sh

Docker Compose 示例

version: '3.8'

services:
  frontend:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - backend
    restart: unless-stopped

  backend:
    image: backend-service:latest
    ports:
      - "8080:8080"
    restart: unless-stopped

常见问题

Q1: SPA 路由 404 错误

原因: Nginx 找不到对应的文件

解决方案:

# 配置 try_files所有路由返回 index.html
location / {
    try_files $uri $uri/ /index.html;
}

Q2: API 代理跨域问题

原因: CORS 配置不正确

解决方案:

location /api/ {
    proxy_pass http://backend:8080/;
    
    # 添加 CORS 头
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
    
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

Q3: 静态资源缓存问题

原因: 浏览器缓存了旧版本

解决方案:

# HTML 文件不缓存
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-store, no-cache, must-revalidate";
}

# JS/CSS 使用版本号或哈希,可以长期缓存
location ~* \.(js|css)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Q4: 容器启动后立即退出

原因: Nginx 配置文件语法错误

解决方案:

# 测试配置文件
docker run --rm -v ./nginx.conf:/etc/nginx/conf.d/default.conf \
  nginx:1.28.0-alpine nginx -t

# 查看容器日志
docker logs <container-id>

参考资源