Docker 容器化 Node.js 应用完全指南
本文手把手将 Node.js 应用 Docker 化:编写 Dockerfile、多阶段构建、镜像瘦身、.dockerignore、docker-compose 多容器编排与开发环境热重载,附生产部署最佳实践。
Node.js 容器化的核心是一份分层的 Dockerfile:选官方 node 镜像、先拷贝 package.json 装依赖(利用构建缓存)、再拷贝源码、CMD 启动。生产镜像用多阶段构建砍掉 devDependencies,配合 .dockerignore 排除 node_modules,Compose 管理应用+数据库多容器。*
Dockerfile:从能跑到好用
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
四个关键决策:先装依赖再拷源码(代码天天变、依赖不常变,便于复用依赖层缓存);alpine 基础镜像(检查原生依赖的 musl 兼容性;有兼容问题时可选 Debian slim);USER node(不用 root 跑进程,安全基线);npm ci 而非 install(严格按 lock 安装,构建可复现)。
配套 .dockerignore:
node_modules
npm-debug.log
.git
.env
coverage
构建与运行
docker build -t myapp:1.0 .
docker run -d -p 3000:3000 --name myapp myapp:1.0
docker logs -f myapp
docker exec -it myapp sh
端口映射 -p 宿主机:容器;环境变量用 -e KEY=VALUE 或 --env-file 注入,密钥绝不打进镜像层。
Compose:编排应用与依赖
真实应用要连数据库,Compose 一键拉起整套环境:
services:
app:
build: .
ports: ['3000:3000']
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/myapp
depends_on: [db]
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
volumes: [pgdata:/var/lib/postgresql/data]
volumes:
pgdata:
docker compose up -d 启动,docker compose logs -f app 看日志,docker compose down 全部拆除。
开发环境:容器内热重载
开发场景把源码挂进容器,实现"改代码即生效":
services:
app:
build: .
volumes:
- .:/app
- /app/node_modules # 匿名卷保护容器内依赖
command: npx tsx watch src/index.ts
生产最佳实践清单
- 多阶段构建,最终镜像不含构建工具与 devDependencies;
- 健康检查:
HEALTHCHECK CMD wget -qO- http://localhost:3000/health || exit 1; - 优雅停机:Node 处理 SIGTERM(Node 直接作为 PID 1 时收不到信号,用
CMD ["node", ...]exec 形式即可正确接收); - 资源限制:K8s/Compose 中设 memory limit,Node 堆内存配合
--max-old-space-size; - 镜像扫描:CI 中跑 trivy 之类的漏洞扫描。
关联容器资源与 Node.js 错误
Node.js 在容器内退出时,先对照容器资源限制、退出状态和应用错误。观测云的容器采集可提供资源、对象及日志数据;应用 APM 仍需要在镜像中安装并初始化 SDK。给日志与链路设置一致的 service、env、version,并保留容器标识,才能在多副本发布时区分故障实例与正常实例。
接入文档:容器与 Kubernetes 采集 · 日志采集 · Node.js APM 接入。
常见问题(FAQ)
Q:alpine 镜像有什么坑?
musl libc 与 glibc 的差异会让部分原生模块(node-sass、bcrypt 老版本、某些 sharp 场景)编译失败。遇到时换 node:22-slim(Debian 系、体积适中)即可,别死磕 alpine。
Q:容器里 Node 内存超限被杀(OOMKilled)怎么排查?
容器 memory limit 与 Node 堆上限要协调:容器限 512MB 就设 --max-old-space-size=384(留出堆外内存)。Node 18+ 在容器内会自动感知 cgroup 限制,老版本需要显式设置。
Q:多阶段构建里 devDependencies 的 TypeScript 编译怎么做?
加一个 builder 阶段:装全部依赖 → npm run build 产出 dist → runner 阶段只拷 dist 和生产依赖。最终镜像里没有 TypeScript 和源码,体积与安全双赢。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。