Loading...
Loading...
Compare original and translation side by side
.nvmrcpackage.jsonKey Insight: Unlike server-side Node.js apps, React apps only need Node.js for building—the runtime is static files served by Nginx. This reduces image size from ~1GB to ~50MB.
.nvmrcpackage.json核心要点:与服务端Node.js应用不同,React应用仅在构建阶段需要Node.js——运行时由Nginx提供静态文件服务。这可将镜像大小从约1GB缩减至约50MB。
| Scenario | Runtime Image | Compressed Size |
|---|---|---|
| Standard Nginx | | ~45 MB |
| Non-root (recommended) | | ~45 MB |
| With Brotli compression | | ~55 MB |
| 场景 | 运行时镜像 | 压缩后大小 |
|---|---|---|
| 标准Nginx | | ~45 MB |
| 非root用户(推荐) | | ~45 MB |
| 带Brotli压缩 | | ~55 MB |
undefinedundefinedundefinedundefinednginx.confserver {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Hide Nginx version
server_tokens off;
# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}
# Cache hashed assets forever (Vite generates unique hashes)
location ~* \.(?:css|js)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Cache static assets
location ~* \.(?:ico|gif|jpe?g|png|svg|woff2?|ttf|eot)$ {
expires 6M;
add_header Cache-Control "public, max-age=15552000";
}
# No cache for index.html (entry point must always be fresh)
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml+rss application/atom+xml image/svg+xml;
# Deny hidden files
location ~ /\. {
deny all;
}
}nginx.confserver {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 隐藏Nginx版本
server_tokens off;
# SPA路由 - 为所有路由返回index.html
location / {
try_files $uri $uri/ /index.html;
}
# 永久缓存带哈希值的资源(Vite会生成唯一哈希值)
location ~* \.(?:css|js)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 缓存静态资源
location ~* \.(?:ico|gif|jpe?g|png|svg|woff2?|ttf|eot)$ {
expires 6M;
add_header Cache-Control "public, max-age=15552000";
}
# 不缓存index.html(入口文件必须始终保持最新)
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml+rss application/atom+xml image/svg+xml;
# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
}undefinedundefined
**Required nginx-main.conf** for non-root operation:
```nginx
worker_processes auto;
pid /var/run/nginx/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}server {
listen 8080;
# ... rest of config
}
**非root运行必需的nginx-main.conf**:
```nginx
worker_processes auto;
pid /var/run/nginx/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}server {
listen 8080;
# ... 其余配置保持不变
}undefinedundefined
Build with:
```bash
docker build \
--build-arg VITE_API_URL=https://api.example.com \
--build-arg VITE_APP_TITLE="My App" \
-t myapp:prod .VITE_
构建命令:
```bash
docker build \
--build-arg VITE_API_URL=https://api.example.com \
--build-arg VITE_APP_TITLE="My App" \
-t myapp:prod .VITE_public/config.js.templatewindow.__ENV__ = {
VITE_API_URL: "__VITE_API_URL__",
VITE_FEATURE_FLAG: "__VITE_FEATURE_FLAG__"
};docker-entrypoint.sh#!/bin/sh
set -epublic/config.js.templatewindow.__ENV__ = {
VITE_API_URL: "__VITE_API_URL__",
VITE_FEATURE_FLAG: "__VITE_FEATURE_FLAG__"
};docker-entrypoint.sh#!/bin/sh
set -e
**Step 3**: Update Dockerfile:
```dockerfile
FROM nginx:stable-alpine AS production
RUN apk add --no-cache gettext
COPY --from=builder /app/dist /usr/share/nginx/html
COPY public/config.js.template /usr/share/nginx/html/config.js.template
COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]const apiUrl = window.__ENV__?.VITE_API_URL || import.meta.env.VITE_API_URL;
**步骤3**:更新Dockerfile:
```dockerfile
FROM nginx:stable-alpine AS production
RUN apk add --no-cache gettext
COPY --from=builder /app/dist /usr/share/nginx/html
COPY public/config.js.template /usr/share/nginx/html/config.js.template
COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]const apiUrl = window.__ENV__?.VITE_API_URL || import.meta.env.VITE_API_URL;undefinedundefined
Build targets:
```bash
构建目标:
```bashundefinedundefinedvite.config.tsimport { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0', // Listen on all interfaces
port: 5173,
watch: {
usePolling: true, // Required for Docker file watching
},
hmr: {
host: 'localhost',
port: 5173,
},
},
});usePolling: truevite.config.tsimport { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0', // 监听所有网络接口
port: 5173,
watch: {
usePolling: true, // Docker文件监听必需
},
hmr: {
host: 'localhost',
port: 5173,
},
},
});usePolling: trueFROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .undefinedundefinedRUN \
npm ciRUN \
npm ciundefinedundefinedundefinedundefined.dockerignorenode_modules
npm-debug.log*
dist
build
.git
.gitignore
*.md
.env
.env.*
.vscode
.idea
coverage
*.test.*
*.spec.*
__tests__
Dockerfile*
docker-compose*
.dockerignore.dockerignorenode_modules
npm-debug.log*
dist
build
.git
.gitignore
*.md
.env
.env.*
.vscode
.idea
coverage
*.test.*
*.spec.*
__tests__
Dockerfile*
docker-compose*
.dockerignorebuild/dist/REACT_APP_VITE_FROM node:22-alpine AS builder
WORKDIR /app
ARG REACT_APP_API_URL
ENV REACT_APP_API_URL=$REACT_APP_API_URL
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:stable-alpine AS production
COPY /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]build/dist/REACT_APP_VITE_FROM node:22-alpine AS builder
WORKDIR /app
ARG REACT_APP_API_URL
ENV REACT_APP_API_URL=$REACT_APP_API_URL
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:stable-alpine AS production
COPY /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]try_files $uri $uri/ /index.htmldist/build/USER nginxNODE_OPTIONSusePolling: truetry_files $uri $uri/ /index.htmldist/build/USER nginxNODE_OPTIONSusePolling: true