容器化部署:前端静态 + Nginx
核心原则:Nginx单独容器,前端dist作为静态资源挂载进Nginx容器;后端另起容器,完全解耦
整体架构(容器版)
浏览器 → Nginx容器(静态资源+反向代理API) → 后端服务容器(Java/Go等)
- Nginx容器职责:托管前端dist、history路由兜底、SSL、限流、请求转发、日志输出
- 前端不单独起容器!前端只是一堆静态文件,没必要单独容器,这是很多人踩的坑。
方案一:Nginx官方基础镜像,dist用宿主机目录挂载(volume bind mount) ,更新前端只替换宿主机dist文件,
nginx -s reload,不用重建镜像、不用重启容器 。方案二:前端打包进独立容器(Nginx塞在前端镜像里),每次改前端就要重新构建镜像,版本回滚麻烦。
两种容器方案(二选一)
方案1:Bind挂载
宿主机存放前端dist、nginx配置,直接挂载到Nginx容器内。
优点:
- 更新前端:只需要堡垒机上传覆盖宿主机dist目录,不需要构建镜像
- 回滚简单:宿主机备份dist文件夹,需要回滚直接替换
- 配置文件直接在宿主机编辑,方便信息科查看审计
- 离线交付简单,镜像只需要提前导出nginx基础镜像
目录规划(宿主机,虚拟机/麒麟服务器)
/opt/hospital-web/
├── dist/ # 前端打包产物dist
├── nginx/
│ ├── conf.d/
│ │ └── hospital.conf # 业务nginx配置
│ ├── nginx.conf # nginx主配置(少量修改)
│ └── ssl/ # 证书目录(HTTPS)
└── logs/ # nginx日志,宿主机持久化(等保要求)
Docker run 启动命令示例
bash
docker run -d \
--name hospital-nginx \
--restart=always \
-p 80:80 \
-p 443:443 \
# 挂载静态资源
-v /opt/hospital-web/dist:/usr/share/nginx/html \
# 挂载自定义配置
-v /opt/hospital-web/nginx/conf.d:/etc/nginx/conf.d \
-v /opt/hospital-web/nginx/nginx.conf:/etc/nginx/nginx.conf \
# 挂载证书
-v /opt/hospital-web/nginx/ssl:/etc/nginx/ssl \
# 日志持久化到宿主机
-v /opt/hospital-web/logs:/var/log/nginx \
# 安全:禁止特权,使用非root用户(等保重点)
--user 101:101 \
--read-only \
--tmpfs /tmp \
nginx:1.24-alpine
--user 101:101:nginx默认uid,容器内部不能root运行 ,等保必查项;--read-only只读文件系统,缩小攻击面。
平滑更新前端(非常重要,医院不能断业务)
- 宿主机备份旧dist:
cp -r /opt/hospital-web/dist /opt/hospital-web/dist_bak_20261009 - 上传新dist覆盖
- 进入容器校验配置并reload:
bash
# 校验nginx配置
docker exec hospital-nginx nginx -t
# 平滑重载,不中断连接
docker exec hospital-nginx nginx -s reload
⚠️ 不要
docker restart,会直接断开用户会话!
方案2:构建自定义Nginx镜像(适合交付包固化,版本严格管控场景)
把dist、nginx配置打包进镜像。
- 适合:版本强审计,每次发布一个镜像tag,镜像作为交付物;缺点:改前端必须重新build镜像,回滚需要拉旧镜像
Dockerfile示例
dockerfile
# x86: nginx:1.24-alpine
# arm麒麟: nginx:1.24-alpine-arm64
FROM nginx:1.24-alpine
# 关闭版本号,安全头
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/conf.d /etc/nginx/conf.d
COPY ./dist /usr/share/nginx/html
COPY ./nginx/ssl /etc/nginx/ssl
# 安全:创建普通用户,不root运行
RUN addgroup -g 101 -S nginx && adduser -S -u 101 -G nginx nginx
USER nginx
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
构建:docker build -t hospital-web:v1.0.0 .
交付方式:
docker save hospital-web:v1.0.0 -o hospital-web-v1.0.0.tar,拷贝到医院内网机器docker load导入。缺点:每次前端小改动都要打包镜像,运维不如bind mount方便,而且这样镜像包会比较大。
综合下来,我觉得选方案二的同时,只COPY一个文件index.html到容器中,其他静态资源放到一些静态资源服务器上去,然后当浏览器加载的时候,index.html就会去静态资源服务器去拉取对应的文件。
担心点:医院给我们分配一个新域名,如果域名还是解析到他们本身已有的服务器上了,那么他们那个服务器是否已经有nginx了,如果已经有了,那么我们的nginx就会失效,因为我们也要用80和443端口。想了下应该不存在,访问域名直接就走我们的nginx了,会设置server_name
server {
listen 80;
server_name cdn.cmvalue.com;
add_header Access-Control-Allow-Origin *;
location /repo/web/homepage {
alias /Users/zhangyu/web/yongliu/homepage/;
index index.html index.htm index.php;
autoindex on;
try_files $uri $uri/ /index.html;
}
}
推荐:Docker Compose 编排(单机容器,医院最常用)
把Nginx + 后端服务写在compose.yml,一键启停,交付简单。
docker-compose.yml
yaml
version: '3.8'
services:
hospital-nginx:
image: nginx:1.24-alpine
container_name: hospital-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /opt/hospital-web/dist:/usr/share/nginx/html
- /opt/hospital-web/nginx/conf.d:/etc/nginx/conf.d
- /opt/hospital-web/nginx/ssl:/etc/nginx/ssl
- /opt/hospital-web/logs:/var/log/nginx
user: "101:101"
read_only: true
tmpfs: /tmp
depends_on:
- hospital-api
hospital-api:
image: hospital-api:v1.0.0
container_name: hospital-api
restart: always
# 后端端口,不对外暴露,只给同compose网络内nginx访问
expose:
- "8080"
volumes:
# 后端日志、数据持久化
- /opt/hospital-api/logs:/app/logs
启动:docker-compose up -d
重载nginx:docker-compose exec hospital-nginx nginx -s reload
Compose 内部是默认bridge网络,nginx可以直接用服务名
hospital-api:8080代理后端,不需要宿主机IP,非常方便。Nginx代理API地址就写
proxy_pass http://hospital-api:8080/
Nginx配置(容器内,history SPA,可直接放进conf.d)
nginx
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
server_tokens off;
# 静态缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2|ttf)$ {
expires 30d;
add_header Cache-Control "public,max-age=2592000";
add_header X-Content-Type-Options nosniff;
}
# history路由兜底
location / {
try_files $uri $uri/ /index.html;
}
# API转发到compose内后端服务
location /api/ {
proxy_pass http://hospital-api:8080/;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
医院容器化重点约束(等保+内网离线,重中之重)
-
离线镜像交付
医院内网隔离,不能pull镜像。提前在外网机器下载对应架构镜像,
docker save导出tar包,拷贝到医院服务器,docker load导入。- x86:
nginx:1.24-alpine - ARM鲲鹏/飞腾麒麟:
nginx:1.24-alpine拉arm64版本,不要混用x86镜像,会报错。
- x86:
-
安全(等保必查)
- 容器禁止--privileged特权模式
- 容器内进程不能root,必须指定普通user运行
- 日志挂载宿主机,日志留存≥6个月,不能只存在容器内部(容器删了日志丢失,直接等保扣分)
- 不要开放Docker远程API(2375端口),高危
-
网络
- 后端容器尽量
expose,不ports映射到宿主机,仅Nginx暴露80/443;后端服务只能被Nginx访问,缩小攻击面 - 提前和信息科确认服务器防火墙、堡垒机上传包大小限制
- 后端容器尽量
-
版本更新与回滚流程(容器bind挂载方案)
备份dist → 上传新dist → docker exec nginx nginx -t → nginx -s reload 异常:直接把备份dist覆盖回去,再次reload,回滚几十秒完成
方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Bind挂载Nginx容器(推荐) | 绝大多数医院私有化容器交付 | 前端更新只替换静态文件,无需重制镜像,回滚简单,运维友好 | 宿主机要管理dist目录 |
| 自定义Nginx镜像打包dist | 版本强审计,需要镜像作为交付物 | 交付包完整,环境一致性强 | 前端改动就要重新build镜像,发布笨重 |
如果你需要,我可以给你:
- 完整可直接交付的离线交付文档(包含镜像导出导入脚本、启停、回滚脚本)
- 配套的健康检查脚本(给医院运维用,检测页面/nginx/接口状态)
- HTTPS版本完整nginx配置
要不要我顺便写一份离线镜像导出、导入脚本 + 发布/回滚shell脚本?