Nginx 容器化部署看似简单——docker run -d -p 80:80 nginx 就能跑——但生产里要解决的是:怎么把前端 dist、nginx.conf、SSL 证书打包成可复用的镜像,而不是每次新环境都 ssh 进去改配置。这篇把多年自建 Nginx 镜像的套路整理清楚。
阅读对象:前端工程师、运维、需要部署 Web 服务的全栈工程师
覆盖范围:前端 dist 打包 + nginx.conf 模板 + 反向代理 / WebSocket / TCP UDP 四层 + SSL + bitnami/nginx 优势对比
一、为什么需要自建 Nginx 镜像
docker run nginx 看似省事,但生产场景里很快撞墙:
- 前端 dist 在外面:每次新构建都要
docker cp 或 bind mount,配置不统一 - nginx.conf 在外面:集群里 5 台机器 5 份配置,改一行同步半天
- SSL 证书在容器内:Let’s Encrypt 三个月续期,每台机器都跑一遍 certbot
- 个性化配置散落:gzip、autoindex、限流、灰度发布,每个项目都要从头写
自建 Nginx 镜像的本质:把"配置 + 静态资源 + 证书"做成不可变的镜像。新机器拉新镜像即用,配置漂移被堵在镜像层之外。
When to use:
- 同一份前端代码需要部署到多台机器(灰度 / 多机房)
- 业务方只关心"上传代码 + 重启容器",不想碰 nginx.conf
- 团队有统一的反代规范(统一超时、统一 gzip、统一 WebSocket 头)
二、最小化 Dockerfile
1
2
3
| FROM dockerhub.example.com/base/nginx:latest
ADD html /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
|
文件结构:
1
2
3
4
5
| .
├── Dockerfile
├── html
│ └── index.html
└── nginx.conf
|
构建:
1
2
3
| docker build -t dockerhub.example.com/library/nginx:20240730 .
docker run -d --restart=always --name apph5 --net=host \
dockerhub.example.com/library/nginx:20240730
|
关键点:
ADD html 会把整个目录解压到镜像里,比 COPY html 更适合静态文件(虽然官方推荐 COPY,但 ADD 对本地目录行为一致)--net=host 让 Nginx 直接监听宿主机端口,省一层 NAT——H5 活动页常用library/nginx:20240730 用日期做 tag,避免覆盖旧版本
三、生产级 nginx.conf 模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| user root;
worker_processes 1;
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;
keepalive_timeout 65;
# ============ 核心优化 ============
autoindex on; # 目录浏览(文件服务器场景)
gzip on; # 开启 gzip
gzip_buffers 32 4K; # 压缩缓冲
gzip_comp_level 6; # 压缩级别 1-9,6 是性价比甜蜜点
gzip_min_length 1k; # 1K 以下不压缩
gzip_types application/javascript text/css text/xml; # 压缩类型
gzip_disable "MSIE [1-6]\."; # 兼容老 IE
gzip_http_version 1.1; # 最低 HTTP 版本
gzip_vary on; # 传输压缩标志
# ============ 大文件上传 ============
client_max_body_size 2000M; # 上传文件大小限制
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;
# ============ 业务 server ============
server {
listen 10082;
server_name location;
add_header 'Access-Control-Allow-Origin' '*'; # 跨域
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # SPA 路由
# 反向代理后端 API
location ^~ /api/ {
proxy_pass http://localhost:9527/;
}
}
}
|
实战要点:
try_files $uri $uri/ /index.html:SPA 路由必备——直接访问 /user/profile 不 404gzip_types 务必加 application/javascript(不是 text/javascript)——新版 MIME 标准client_max_body_size 2000M:配合后端 Spring Boot 的 spring.servlet.multipart.max-file-size 一起调add_header 'Access-Control-Allow-Origin' '*':只用于开发,生产应该用 https://www.example.com 白名单
四、WebSocket 反向代理
Nginx 反代 WebSocket 关键是保留 Upgrade 头:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # http block 顶部加映射
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# server block 内 location
location /ws/ {
proxy_pass http://localhost:8080/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 长连接超时
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
|
没加 Upgrade 头时浏览器控制台会报 WebSocket connection to 'ws://...' failed: Invalid frame header。
五、四层转发:TCP / UDP 代理
Nginx 从 1.9 开始支持 stream 模块(四层),可以代理 MySQL、Redis、MQTT、SIP 等非 HTTP 协议。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # nginx.conf 顶层加 stream block(与 http 同级)
stream {
log_format proxy '[$time_local] $protocol status:$status, '
'bytes client:$bytes_sent/$bytes_received $session_time, '
'client: $remote_addr, upstream:"$upstream_addr", '
'bytes upstream:$upstream_bytes_sent $upstream_bytes_received';
access_log /var/log/nginx/stream.log proxy;
# MySQL 反代
upstream mysql {
server 10.0.0.1:3306 max_fails=2 fail_timeout=10s;
server 10.0.0.2:3306 max_fails=2 fail_timeout=10s;
}
server {
listen 3306;
proxy_pass mysql;
proxy_connect_timeout 5s;
}
# 端口段转发(PASV FTP 必备)
server {
listen 21100-21110;
proxy_pass 10.0.0.1:$server_port; # $server_port 是动态端口
}
}
|
实战案例:FTP 服务主动模式要在客户端出口网络(防火墙 / NAT)做端口段转发,stream 模块就是干这个的。
六、SSL / HTTPS 完整配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| server {
server_name api.example.com;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# 协议版本(生产只开 TLS 1.2+)
ssl_protocols TLSv1.2 TLSv1.3;
# 加密套件
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
# session 复用
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# HTTP 强转 HTTPS
error_page 497 https://$host$request_uri;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 80 强转 443
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
|
证书挂载(推荐用 bind mount 而非 ADD 进镜像):
1
2
3
| docker run -d \
-v /etc/letsencrypt/live/api.example.com:/etc/nginx/ssl:ro \
...
|
证书 3 个月续期时 nginx -s reload 一下,不用重启容器。
七、bitnami/nginx 镜像优势
bitnami/nginx 相对官方 nginx 镜像有几个明显优势:
| 特性 | 官方 nginx | bitnami/nginx |
|---|
| 非 root 用户运行 | 默认 root(安全风险) | 默认 uid 1001 |
| 默认 HSTS / 安全头 | 无 | 有 |
| 健康检查 | 无 | HEALTHCHECK 内置 |
| 日志切分 | 无 | 接入 stdout/stderr |
| 镜像体积 | ~ 140MB | ~ 90MB |
生产强烈推荐 bitnami 镜像。root 运行的 nginx 容器一旦被攻破就拿到宿主 root——bitnami 默认 nginx 用户运行,减少 90% 的攻击面。
八、生产部署清单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 准备前端 dist
npm run build # 生成 dist/
# 2. 准备 nginx.conf(用上面的模板)
# 3. 准备 SSL 证书
mkdir -p ssl && cp /etc/letsencrypt/live/example.com/* ssl/
# 4. 构建镜像
docker build -t dockerhub.example.com/library/web:20240730 .
# 5. 推到 Harbor
docker push dockerhub.example.com/library/web:20240730
# 6. 生产部署
docker run -d --name web --restart=always \
--network=web \
-p 443:443 -p 80:80 \
-v /data/web/ssl:/etc/nginx/ssl:ro \
dockerhub.example.com/library/web:20240730
|
九、监控与日志
日志接入:
1
2
3
4
5
6
7
| # 用 Loki / ELK 收集,docker-compose 里加 logging driver
logging:
driver: loki
options:
loki-url: "http://internal.example.com:3100/loki/api/v1/push"
max-size: "50m"
max-file: "10"
|
健康检查:
1
2
3
4
5
6
| location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1; # 只允许内网
deny all;
}
|
容器外查:
1
2
3
4
5
| curl http://container-ip/nginx_status
# Active connections: 2
# server accepts handled requests
# 100 100 200
# Reading: 0 Writing: 1 Waiting: 1
|
十、常见坑位
10.1 反代后端丢失 HTTPS 信息
后端 Spring Boot 拿到 request.getScheme() 是 http 不是 https——因为 nginx 用 HTTP 协议连后端。
修复:后端配 server.tomcat.remoteip.protocol-header=X-Forwarded-Proto,或 Nginx 显式传:
1
| proxy_set_header X-Forwarded-Proto $scheme;
|
10.2 SPA 路由 404
直接访问 https://app.example.com/user/profile 返回 404。
修复:try_files $uri $uri/ /index.html;——所有未匹配路径都返回 index.html,由前端路由处理。
10.3 WebSocket 反复断连
症状:WSS 连接 60 秒断一次。
原因:默认 proxy_read_timeout 60s,WebSocket 是长连接,超过 60 秒没数据就被切断。
修复:proxy_read_timeout 300s; 或更长。
10.4 gzip 对小文件无效果
gzip_min_length 1k; 控制 1KB 以下的文件不压缩——gzip_types 只决定"压缩什么类型",文件大小阈值由 min_length 控制。
2024+ 视角补充
本文写于 2023-09,2024-2026 期间 Nginx 自建镜像关键演进:
- Nginx 1.27 LTS(2024-11):HTTP/3(QUIC)GA;OpenSSL 3.x;动态 SSL 证书加载
- Nginx 1.28 LTS(2025-Q4 预期):ACMEv2 自动证书(不用 certbot);BoringSSL 可选
- Nginx Unit 1.34+(2024-2026):应用服务器模式(Go / Node / Python / Java / PHP)——Web + App Server 一体
- Nginx Ingress 1.12+(K8s):Gateway API GA;IPV6 双栈;WAF 集成
- OpenResty 1.25+:Lua 生态成熟,API Gateway / WAF 场景仍是首选
- bitnami/nginx 镜像:2024+ 仍维护,非 root 用户 / 健康检查仍是优势
- 替代品(2024+ 视角):
- Caddy 2.x:自动 HTTPS / Caddyfile 简洁——新项目首选
- Traefik 3.x:K8s 原生 / 自动服务发现
- HAProxy 3.x:四层代理性能优于 Nginx——数据库 / TCP 代理首选
实战建议(2025-2026 视角):
- 传统 Web 反代 / 静态站 → Nginx 1.27 LTS 仍是生产首选
- 想要最少配置 / 自动 HTTPS → Caddy 2.x
- K8s 入口 → Nginx Ingress 1.12+ / Traefik 3.x / Gateway API
- API Gateway / WAF → OpenResty 1.25+ 仍是稳态
- 数据库 / TCP 代理 → HAProxy 3.x
下一步