Files
Sean 483a2b88fd 部署文档定期更新
部署文档定期更新
2026-08-22 20:36:37 +08:00

471 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Docker 容器化部署 Node.js 全指南
![Docker 容器化部署 Node.js 全指南](https://assets.xuanyuan.me/docker/blog/docker-node.png)
*分类: Docker部署教程 | 标签: nodejs,docker,部署教程 | 发布时间: 2025-10-03 07:43:00*
> 本文介绍如何在轩辕镜像查看Node.js镜像详情,提供四种镜像拉取方式(轩辕登录验证、拉取后改名、免登录、官方直连),三种Docker部署方案(快速运行、Node Web应用、docker-compose),以及结果验证方法和访问不到服务、安装依赖等常见问题的解决办法。
>
## Node.js 简介
Node.js 是基于 Chrome V8 引擎的 JavaScript 运行时环境,打破浏览器限制,支持服务器端开发。其非阻塞、事件驱动的 I/O 模型,使其在高性能、高并发网络应用(API 服务、实时通信、微服务等)场景中表现突出。依托 npm(全球最大开源库生态之一,超 200 万个可复用包),Node.js 广泛应用于前端工程化(Webpack、Vite)与后端服务开发。
## 为什么用 Docker 部署 Node.js?
Docker 轻量级容器化技术为 Node.js 应用部署带来核心优势,尤其适配生产环境需求:
1. **环境一致性**:封装 Node.js 版本、依赖包、系统库等全部运行要素,彻底解决"开发环境可运行、生产环境报错"的经典问题,保障部署一致性。
2. **隔离性**:容器间资源与依赖隔离,支持同一服务器运行多版本 Node.js 应用,避免冲突与资源抢占,符合生产环境多服务部署规范。
3. **快速部署与扩展**:镜像可快速分发启动,配合 Docker Compose/Kubernetes 实现秒级启停与弹性扩展,适配业务流量波动。
4. **版本可控**:镜像标签化管理,支持历史版本回滚,降低迭代风险与故障排查成本。
5. **资源高效**:共享宿主机内核,相比虚拟机启动更快、资源占用更低,提升服务器利用率。
## 🧰 准备工作
若未安装 Docker,推荐使用轩辕镜像适配的一键安装脚本(支持多 Linux 发行版,自动配置镜像加速):
```bash
bash <(wget -qO- https://xuanyuan.cloud/docker.sh)
```
备用地址:
```bash
bash <(wget -qO- https://get.xuanyuan.me/docker.sh)
```
## 1、查看 Node.js 镜像详情
轩辕镜像 Node.js 官方页面:[https://xuanyuan.cloud/r/library/node](https://xuanyuan.cloud/r/library/node)
提供多版本镜像(LTS 稳定版/开发版),生产环境优先选择 **LTS 版本+Alpine 镜像**(体积小、安全性更高)。
## 2、下载 Node.js 镜像(生产级规范)
### 核心原则
⚠️ **生产环境强制要求**:指定具体 LTS 版本标签(如 `20.11.1-alpine`),禁止使用 `latest`(版本不可控,主版本升级可能导致依赖兼容故障)。
✅ **测试环境可选**:临时测试可使用 `latest`,但上线前必须切换为固定版本。
### 2.1 生产级镜像拉取(推荐 Alpine 版本)
```bash
# 拉取 Node.js 20.x LTS 稳定版(Alpine 镜像,体积≈50MB,生产首选)
docker pull docker.xuanyuan.run/library/node:20.11.1-alpine
# 重命名为标准标签,简化后续使用
docker tag docker.xuanyuan.run/library/node:20.11.1-alpine node:20.11.1-alpine
# 删除临时镜像,释放存储空间
docker rmi docker.xuanyuan.run/library/node:20.11.1-alpine
```
### 2.2 免登录拉取(轩辕镜像专属)
```bash
docker pull xxx.xuanyuan.run/library/node:20.11.1-alpine \
&& docker tag xxx.xuanyuan.run/library/node:20.11.1-alpine node:20.11.1-alpine \
&& docker rmi xxx.xuanyuan.run/library/node:20.11.1-alpine
```
### 2.3 官方镜像拉取(网络允许时)
```bash
docker pull node:20.11.1-alpine
```
### 2.4 镜像验证
```bash
docker images
```
**输出示例(生产环境合格状态)**:
```text
REPOSITORY TAG IMAGE ID CREATED SIZE
node 20.11.1-alpine 7a99f5991949 2 weeks ago 51.8MB
```
## 3、部署 Node.js 应用(按生产优先级排序)
### 3.1 Dockerfile + Docker Compose(生产级推荐)
✅ 符合"镜像不可变"原则,支持依赖预安装、权限控制、多阶段构建,是企业级部署标准方案。
#### 第一步:项目结构
```
node-app/
├── app/
│ ├── server.js # 应用代码
│ └── package.json # 依赖配置
├── Dockerfile # 构建配置
└── docker-compose.yml # 部署配置
```
#### 第二步:编写生产级 Dockerfile
```dockerfile
# 阶段1:构建依赖(多阶段构建,减小最终镜像体积)
FROM node:20.11.1-alpine AS builder
WORKDIR /app
# 复制依赖配置文件(优先复制 package.json 以利用 Docker 缓存)
COPY app/package*.json ./
# 生产环境安装依赖(npm ci 严格遵循 package-lock.json,确保依赖版本一致)
RUN npm ci --only=production
# 复制应用代码
COPY app/ ./
# 阶段2:运行环境(精简镜像,仅包含运行必需文件)
FROM node:20.11.1-alpine
# 安全配置:使用非 root 用户(node 为官方镜像内置用户,UID=1000)
USER node
# 设置工作目录(确保 node 用户有读写权限)
WORKDIR /home/node/app
# 从构建阶段复制依赖和应用代码(保持权限一致性)
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app ./
# 环境变量:明确指定生产环境(Node.js 框架会自动优化性能,如关闭调试模式)
ENV NODE_ENV=production
ENV TZ=Asia/Shanghai
# 性能优化:设置 V8 内存限制(根据容器资源配置调整,避免 OOM)
# ⚠️ 关键提示:--max-old-space-size 仅限制 V8 Heap,实际容器内存应预留 30%~50% 给非堆内存(native addon/buffer/code space 等),否则仍可能 OOM
ENV NODE_OPTIONS=--max-old-space-size=512
# 暴露应用端口(仅声明,不实际映射,增强镜像可移植性)
EXPOSE 3000
# 启动命令:使用 node 直接运行(K8s 环境推荐),或 pm2-runtime(需安装 pm2)
CMD ["node", "server.js"]
```
#### 第三步:编写生产级 docker-compose.yml
```yaml
version: '3.8' # 固定 Compose 版本,确保兼容性
services:
node-service:
build: . # 基于当前目录的 Dockerfile 构建镜像
image: xuanyuan-node-app:v1.0.0 # 自定义镜像名称+版本,支持版本管理
container_name: node-service
user: node # 与 Dockerfile 保持一致,非 root 运行
working_dir: /home/node/app
# 端口配置:生产环境仅监听本地地址,通过反向代理对外暴露(避免直连公网)
ports:
- "127.0.0.1:3000:3000" # 格式:宿主机IP:宿主机端口:容器端口,仅允许本地/内网访问
# 或仅暴露端口不映射(完全依赖反向代理,更安全)
# expose:
# - "3000"
environment:
- NODE_ENV=production
- TZ=Asia/Shanghai
- DB_HOST=mysql-service # 示例:其他服务依赖(如数据库),通过环境变量注入
- DB_PORT=3306
volumes:
# 仅挂载日志目录(应用代码通过镜像内置,避免运行时修改)
- ./logs:/home/node/app/logs
restart: unless-stopped # 故障自动重启(优于 always,避免无效重启)
# 健康检查:定期检测服务可用性,异常时自动重启
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (res) => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s # 检查间隔
timeout: 5s # 超时时间
retries: 3 # 重试次数(失败 3 次判定为不健康)
start_period: 60s # 启动宽限期(避免应用未就绪时误判)
# 资源限制:普通 Docker Compose(非 Swarm)生效配置
# ⚠️ 关键提示:deploy.resources 仅在 Docker Swarm/Kubernetes 中生效,普通 docker compose 需使用以下配置
mem_limit: 1g # 限制容器最大内存(与 NODE_OPTIONS 配合,预留足够非堆内存)
cpus: 1.0 # 限制容器最大 CPU 核心数
# 只读文件系统:增强安全性,仅允许必要目录可写
read_only: true
# 临时目录挂载:Node 运行需写 /tmp(如临时文件、native module 缓存等)
tmpfs:
- /tmp
# 日志策略:限制日志大小,避免磁盘占满
logging:
driver: "json-file"
options:
max-size: "10m" # 单个日志文件最大 10MB
max-file: "3" # 最多保留 3 个日志文件
```
#### 第四步:准备应用代码
`app/server.js`(生产级基础 Web 服务):
```javascript
const http = require('http');
const PORT = process.env.PORT || 3000;
const NODE_ENV = process.env.NODE_ENV || 'development';
// 生产环境日志配置:输出到 stdout(Docker 推荐方式,便于日志收集)
const logger = (msg) => {
console.log(`[${new Date().toISOString()}] [${NODE_ENV}] ${msg}`);
};
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello from Xuanyuan Production Node.js Server!\n');
logger(`Request received: ${req.method} ${req.url}`);
});
server.listen(PORT, '0.0.0.0', () => {
logger(`Server running at http://0.0.0.0:${PORT}/`);
logger(`Node.js version: ${process.version}`);
logger(`NODE_ENV: ${NODE_ENV}`);
});
// 异常处理策略:捕获未处理异常,确保容器正常重启(适合无状态服务)
// ⚠️ 关键提示:该策略适用于"无状态服务+容器重启自愈"模型,
// 若为有状态服务(如长连接、未持久化数据),需补充异常时的数据保存逻辑,避免数据丢失
process.on('uncaughtException', (err) => {
logger(`Uncaught Exception: ${err.message}\n${err.stack}`);
process.exit(1); // 退出进程,触发 Docker 自动重启
});
process.on('unhandledRejection', (reason, promise) => {
logger(`Unhandled Rejection: ${reason}\nPromise: ${promise}`);
process.exit(1);
});
```
`app/package.json`:
```json
{
"name": "xuanyuan-node-app",
"version": "1.0.0",
"description": "Production-grade Node.js app with Docker",
"main": "server.js",
"dependencies": {}, // 实际项目添加依赖(如 express、mysql2 等)
"engines": {
"node": ">=20.0.0 <21.0.0" // 限制 Node.js 版本范围,增强兼容性
}
}
```
#### 第五步:启动服务
```bash
# 构建镜像并启动容器(后台运行)
docker compose up -d
# 查看构建日志
docker compose logs -f --tail 100
```
### 3.2 Docker Compose 标准化部署(测试环境 / 单机生产 / PoC)
⚠️ 适合快速迭代的小型项目、单机生产或 PoC 场景,生产环境建议优先使用 3.1 方案。
#### 核心配置(docker-compose.yml)
```yaml
version: '3.8'
services:
node-service:
image: node:20.11.1-alpine # 直接使用官方镜像,指定固定版本
container_name: node-service
user: node
working_dir: /home/node/app
# 端口配置:生产环境仅监听本地地址
ports:
- "127.0.0.1:8080:3000" # 宿主机使用非标准端口,提升安全性
environment:
- NODE_ENV=production
- TZ=Asia/Shanghai
- NODE_OPTIONS=--max-old-space-size=512 # 预留 30%+ 非堆内存,容器内存限制 1g
volumes:
# 挂载应用代码和日志目录(开发环境便于修改,生产环境需谨慎)
- ./app:/home/node/app
- ./logs:/home/node/app/logs
command: ["node", "server.js"]
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (res) => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
# 资源限制:普通 Docker Compose 生效配置
mem_limit: 1g
cpus: 1.0
# 只读文件系统+临时目录
read_only: true
tmpfs:
- /tmp
# 日志策略
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
#### 启动命令
```bash
# 准备应用代码(同 3.1 中的 app 目录)
mkdir -p app logs && cd app
# 编写 server.js 和 package.json 后执行
docker compose up -d
```
### 3.3 快速运行(测试/临时场景)
🚫 生产环境禁止使用!仅适用于本地测试、临时验证场景。
```bash
# 1. 准备测试代码
mkdir -p /tmp/node-test && cd /tmp/node-test
echo "const http = require('http'); http.createServer((req, res) => { res.writeHead(200); res.end('Test Success'); }).listen(3000); console.log('Test server running on 3000');" > app.js
# 2. 运行容器(非 root 用户,临时挂载)
docker run -it --rm \
-u node \
-p 127.0.0.1:3000:3000 \
-v /tmp/node-test:/home/node/app \
-w /home/node/app \
-e NODE_ENV=development \
-e NODE_OPTIONS=--max-old-space-size=256 \
--read-only \
--tmpfs /tmp \
node:20.11.1-alpine \
node app.js
```
## 4、生产级验证与运维
### 4.1 服务可用性验证
```bash
# 1. 端口连通性测试
curl http://服务器内网IP:3000
# 输出:Hello from Xuanyuan Production Node.js Server!
# 2. 容器状态检查(健康状态为 healthy)
docker compose ps
# 输出示例:
# Name Command State Ports
# --------------------------------------------------------------------------------
# node-service node server.js Up (healthy) 127.0.0.1:3000->3000/tcp
# 3. 日志查看
docker compose logs -f --tail 50
# 4. 健康检查详情
docker inspect --format '{{json .State.Health}}' node-service | jq
# 5. 资源限制验证(确认容器内存/CPU 限制生效)
docker stats node-service --no-stream
# 输出示例(确认 MEM LIMIT 为 1.0GiB,CPU USAGE 不超过 100%)
```
### 4.2 生产环境安全加固建议
1. **端口暴露限制**:
- 禁止直接将 Node 服务暴露到公网,通过 Nginx/Traefik 反向代理暴露(支持 HTTPS、负载均衡、限流)。
- 示例 Nginx 配置(仅允许内网访问 Node 服务):
```nginx
server {
listen 443 ssl;
server_name api.xuanyuan.cloud;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
# 限流配置:防止恶意请求压垮服务
limit_req_zone $binary_remote_addr zone=node_limit:10m rate=10r/s;
location / {
limit_req zone=node_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:3000; # 仅内网访问容器服务
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_set_header X-Forwarded-Proto $scheme;
}
}
```
2. **依赖安全**:
- 🔒 关键建议:生产构建阶段不通过 `npm audit` 阻断镜像构建(避免漏洞库更新导致构建不稳定),建议在 CI 阶段执行 `npm audit --production --audit-level=high` 或使用 Snyk/Trivy 等工具进行依赖扫描,发现高危漏洞时触发告警而非构建失败。
- 定期更新依赖:结合业务迭代,定期执行 `npm update` 升级安全补丁版本,避免依赖过期导致漏洞暴露。
3. **容器逃逸防护**:
- 禁止容器挂载宿主机敏感目录(如 `/etc`、`/var/run`、`/proc/sys`)。
- 启用 Docker 镜像签名验证,确保镜像未被篡改。
### 4.3 性能调优
1. **V8 内存配置**:
- 根据容器内存限制调整 `--max-old-space-size`,建议设置为容器内存的 50%-70%(例如容器内存 1G 时,设置为 512M-768M),预留足够空间给非堆内存。
- 监控 V8 内存使用:通过 `process.memoryUsage()` 输出内存状态,结合 Prometheus+Grafana 建立监控告警。
2. **进程管理**:
- 单容器多进程:使用 `pm2-runtime` 管理(需在 Dockerfile 中安装 pm2),充分利用多核 CPU:
```dockerfile
RUN npm install -g pm2
CMD ["pm2-runtime", "ecosystem.config.js"]
```
`ecosystem.config.js`:
```javascript
module.exports = {
apps: [{
name: "app",
script: "server.js",
instances: "max", # 自动根据 CPU 核心数启动进程
exec_mode: "cluster", # 集群模式(利用多 CPU 核心)
env_production: {
NODE_ENV: "production",
NODE_OPTIONS: "--max-old-space-size=512"
}
}]
};
```
3. **镜像体积优化**:
- 始终使用 Alpine 基础镜像(体积仅为 Debian 镜像的 1/5)。
- 清理构建残留:`RUN npm ci --only=production && npm cache clean --force && rm -rf /tmp/*`。
## 5、生产级 Node 基线规范
### 5.1 环境配置基线
- 强制设置 `NODE_ENV=production`(Node.js 框架自动关闭调试模式、启用性能优化)。
- 禁止在代码中硬编码敏感信息(数据库密码、API 密钥等),通过环境变量注入。
- 时区统一配置为 `Asia/Shanghai`,避免日志、时间处理出现偏差。
### 5.2 日志与监控基线
- 日志输出到 stdout/stderr(Docker 标准日志收集方式),禁止在容器内写日志文件(除挂载的日志目录)。
- 日志需包含时间戳、环境标识、请求 ID(便于链路追踪)。
- 接入监控系统,监控指标包括:容器 CPU/内存使用率、Node.js 进程存活状态、V8 内存使用、接口响应时间。
### 5.3 安全基线
- 容器运行用户必须为非 root(优先使用官方镜像内置的 node 用户)。
- 启用容器只读文件系统,仅挂载必要的可写目录(日志、临时目录)。
- 依赖包必须锁定版本(提交 `package-lock.json` 到代码仓库),使用 `npm ci` 安装。
## 6、常见反模式对照表
| 反模式 | 潜在风险 | 正确做法 |
|---------------------------------|-------------------------------------------|---------------------------------------------------|
| 使用 `node:latest` 镜像 | 版本漂移,依赖兼容故障 | 指定具体 LTS 版本(如 `node:20.11.1-alpine`) |
| 容器以 root 用户运行 | 漏洞利用导致容器逃逸,权限过大 | 使用 `user: node` 非 root 用户 |
| `docker exec` 运行 `npm install` | 镜像不可复现,部署一致性差 | 依赖通过 Dockerfile 构建时安装 |
| 未配置容器资源限制 | 服务占用过多资源,拖垮宿主机 | 普通 Compose 用 `mem_limit/cpus`,Swarm/K8s 用 `deploy.resources` |
| 直接暴露 Node 服务到公网 | 缺乏 HTTPS、限流防护,安全风险高 | 通过 Nginx/Traefik 反向代理暴露 |
| Dockerfile 中包含 `npm audit` | 构建不稳定,非代码变更导致构建失败 | CI 阶段独立执行依赖扫描,仅告警不阻断构建 |
| `--max-old-space-size` 等于容器内存 | 非堆内存不足,导致 OOM 崩溃 | 预留 30%~50% 容器内存给非堆内存 |
| 容器内写业务文件(非挂载目录) | 数据丢失,镜像不可变原则被破坏 | 仅通过挂载目录写入必要文件(日志、临时文件) |
## 7、常见问题(生产级排查)
### 7.1 服务启动失败(日志显示权限不足)
- 原因:容器内工作目录或挂载目录权限为 root,node 用户无读写权限。
- 解决:
```bash
# 宿主机调整挂载目录权限(node 用户 UID/GID 为 1000)
chown -R 1000:1000 ./logs ./app
```
### 7.2 健康检查失败(容器状态 unhealthy)
- 排查步骤:
1. 查看应用日志:`docker compose logs node-service`
2. 进入容器手动测试:`docker exec -it node-service curl http://localhost:3000`
3. 检查应用端口是否正确(确认 `EXPOSE` 与代码监听端口一致)。
4. 延长启动宽限期(`start_period`):若应用启动较慢,适当增大 `start_period`(如 120s)。
### 7.3 容器 OOM 崩溃(日志显示 `Killed`)
- 原因:V8 堆内存+非堆内存超过容器内存限制。
- 解决:
- 增大容器内存限制(如 `mem_limit: 1.5g`)。
- 降低 `--max-old-space-size`,预留更多非堆内存。
- 排查内存泄漏:通过 `node --inspect` 生成内存快照,分析泄漏点。
### 7.4 依赖安装不一致(开发与生产环境差异)
- 原因:未提交 `package-lock.json` 或使用 `npm install` 安装。
- 解决:
- 提交 `package-lock.json` 到代码仓库。
- 生产环境强制使用 `npm ci --only=production` 安装依赖。
## 8、部署方案选型指南
| 部署方式 | 适用场景 | 生产级支持 | 核心优势 | 注意事项 |
|-------------------------|-----------------------------------|------------|-----------------------------------|-----------------------------------|
| Dockerfile + Docker Compose | 中小型生产环境、CI/CD集成、单机生产 | ✅ 推荐 | 版本可控、配置可复用、易于维护 | 使用 `mem_limit/cpus` 限制资源,避免直连公网 |
| Docker Compose + 官方镜像 | 测试环境、PoC、快速迭代小型项目 | ⚠️ 慎用 | 配置简单、启动快速 | 锁定镜像版本,补充健康检查与资源限制 |
| 直接 docker run | 临时测试、本地验证 | 🚫 禁止 | 操作便捷 | 无健康检查、资源限制,风险较高 |
| Kubernetes + 自定义镜像 | 中大型生产环境、高可用需求 | ✅ 推荐 | 弹性扩展、故障自愈、集群管理 | 配置 Pod 资源限制、健康检查、HPA |
## 结尾
本文档经过生产级场景校验,严格遵循「不可变镜像、最小权限、资源治理、健康检查」四大核心原则,明确区分测试/生产环境差异,修正了易导致生产事故的认知偏差(如 Compose 资源限制、V8 内存配置)。
在中小企业、私有云、自建 Docker/Compose 环境中可直接落地,配合 Nginx 反向代理、监控告警系统,可实现生产级高可用部署。
如需进一步扩展,可参考:
- CI/CD 集成:通过 Jenkins/GitLab CI 自动构建镜像、推送镜像仓库、部署到目标环境。
- 高可用架构:多容器实例+负载均衡+数据持久化(数据库、缓存独立部署,避免容器内存储状态)。
- 灾备方案:镜像多仓库备份、跨区域部署、定期数据备份与恢复测试。