# Node.js 前端 Dockerfile 模板 Node.js 前端项目的 Docker 容器化模板,使用 Nginx 提供静态文件服务。 ## 模板类型 ### 1. CI 构建镜像(Dockerfile.ci) **适用场景**:在 CI/CD 中使用,静态文件已在外部构建完成。 **优点**: - 镜像体积最小(~20MB) - 构建速度快 - 适合生产环境 ```dockerfile # 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 缓存加速 ```dockerfile # ============================================ # 构建阶段 # ============================================ 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)构建不同配置。 ```dockerfile # ============================================ # 构建阶段 # ============================================ 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;"] ``` **构建命令**: ```bash # 生产环境 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 配置) ```nginx 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 代理) ```nginx 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(完整生产配置) ```nginx # 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 ```nginx 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; # ... 其他配置 } ``` ### 防盗链 ```nginx location ~* \.(jpg|jpeg|png|gif|svg)$ { valid_referers none blocked example.com *.example.com; if ($invalid_referer) { return 403; } expires 1y; } ``` ### 限流 ```nginx # 在 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; } ``` ### 安全头 ```nginx 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. 使用多阶段构建 ```dockerfile # 分离构建和运行环境,只保留必要文件 FROM node:22-alpine AS builder # ... 构建步骤 FROM nginx:1.28.0-alpine # 只复制构建产物 COPY --from=builder /build/dist /usr/share/nginx/html ``` ### 2. 利用构建缓存 ```dockerfile # 先复制依赖文件 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 配置 ```nginx # 启用 sendfile sendfile on; # 启用 tcp_nopush tcp_nopush on; # 启用 tcp_nodelay tcp_nodelay on; # 设置 keepalive 超时 keepalive_timeout 65; # 禁用访问日志(生产环境可选) access_log off; ``` ## 构建命令示例 ### 本地构建 ```bash # 基础构建 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 构建 ```bash # 使用 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 \ . ``` ## 运行容器示例 ```bash # 基础运行 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 # 进入容器调试 docker exec -it /bin/sh ``` ## Docker Compose 示例 ```yaml 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 找不到对应的文件 **解决方案**: ```nginx # 配置 try_files,所有路由返回 index.html location / { try_files $uri $uri/ /index.html; } ``` ### Q2: API 代理跨域问题 **原因**: CORS 配置不正确 **解决方案**: ```nginx 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: 静态资源缓存问题 **原因**: 浏览器缓存了旧版本 **解决方案**: ```nginx # 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 配置文件语法错误 **解决方案**: ```bash # 测试配置文件 docker run --rm -v ./nginx.conf:/etc/nginx/conf.d/default.conf \ nginx:1.28.0-alpine nginx -t # 查看容器日志 docker logs ``` ## 参考资源 - [Node.js 官方 Docker 指南](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/) - [Nginx 官方文档](https://nginx.org/en/docs/) - [Docker 多阶段构建文档](https://docs.docker.com/build/building/multi-stage/) - [前端构建工具文档](https://vitejs.dev/guide/)