- 新增 Go、Node.js、Rust 服务的 Dockerfile 模板 - 新增 Rust 快速参考指南 - 新增 Rust 后端工作流模板 - 优化 create-runner.md,明确 host 网络模式为缓存必需条件 - 更新 gitea skill 主文档
632 lines
14 KiB
Markdown
632 lines
14 KiB
Markdown
# 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 <container-id>
|
||
|
||
# 进入容器调试
|
||
docker exec -it <container-id> /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 <container-id>
|
||
```
|
||
|
||
## 参考资源
|
||
|
||
- [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/)
|