Docker 里跑多个 Next.js 开发服务把宿主机搞死了?这里有套救命方案
前两天在群里看到有同学吐槽:在 Docker 容器里跑几个 Next.js 开发服务,没过多久整个宿主机就卡死不动了,连 SSH 都连不上,只能强制重启。这事儿说实话我也踩过坑,今天就来说说为什么会这样,以及有哪些实用方案能帮你避开这个坑。
先搞清楚为什么会卡死
Next.js 的开发模式(就是那个 next dev)可不是个轻量级的货色。它背后干的事儿可多了:
- 启动完整的 Webpack 编译器
- 开启文件监听,实时检测代码变化
- 运行热模块替换(HMR),一改代码就自动刷新浏览器
- 维护一大堆缓存
这一套组合拳下来,CPU、内存、磁盘 I/O 都得被狠狠占用。你在同一台机器上跑多个这样的服务,资源需求就会成倍增长。结果就是:
- 内存被吃光 → 系统疯狂换页,响应变得奇慢无比,或者直接触发 OOM-Killer 杀进程
- CPU 被抢光 → 所有监听和编译进程在抢 CPU,系统调度器都来不及响应你的操作
- 磁盘 I/O 爆满 → 大量文件读写让硬盘忙不过来
简单说,就是资源不够用了,宿主机被“撑死”了。
第一步:先诊断一下,看看到底是哪个资源占满了
别上来就瞎改,先看看实际情况:
# 看所有容器的资源占用情况
docker stats如果你发现 CPU 长期超过 80%,或者内存快爆了,那基本上就是 Next.js 开发服务在搞鬼。你也可以进容器里看看:
docker exec -it <container> /bin/sh
top第二步:给 Docker 容器加上资源限制(最直接的救命招)
既然是资源不够,那就限制一下每个容器能用多少。这个方法简单粗暴但很有效。
用 docker run 参数限制
docker run -d \
--cpus=1 \
--memory=1g \
-p 3000:3000 \
your-next-dev-image--cpus=1 表示只能用 1 核 CPU,--memory=1g 表示内存最多用 1G。超过这个限制,容器会被直接杀掉,不会把宿主机拖下水。
用 docker-compose 配置(推荐)
# docker-compose.yml
version: "3.8"
services:
next-dev:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
deploy:
resources:
limits:
cpus: "1.5"
memory: 1G
reservations:
cpus: "0.5"
memory: 512M如果你用的是老版本的 docker-compose,可以这样写:
services:
next-dev:
build: .
ports:
- "3000:3000"
mem_limit: 1g
cpu_shares: 1024对了,如果你用的是 Docker Desktop(Mac 或 Windows),记得在设置里把 Docker 的全局内存调大一点,默认 2GB 可能不够,建议至少 4GB。
第三步:优化 Next.js 本身,让它少吃点资源
试试 Turbopack(如果用 Next.js 13+)
Next.js 13 引入的 Turbopack 比传统 Webpack 快很多,而且内存占用明显更低。可以这样启动:
npm run dev -- --turbo
# 或者
yarn dev --turbo这招在不少项目上都能降低 50% 以上的内存占用。
给 Node 设置堆内存上限
在容器里限制 Node 进程的内存使用:
# 环境变量
NODE_OPTIONS="--max-old-space-size=512"这个数值可以根据实际情况调整,开发模式下 512MB 差不多够用了。
关闭不必要的缓存
在 next.config.js 里加这么几行:
module.exports = {
cache: false, // 关闭缓存
reactStrictMode: false, // 关闭严格模式
};关闭缓存后每次启动都会重新编译,首次启动会慢一点,但长期内存占用会明显降低。
第四步:实在不行就用生产模式(最省资源的方法)
如果你不需要热更新,直接用生产模式就行。开发模式在生产环境里本来就是不必要的。
写一个多阶段的 Dockerfile:
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*. ./
RUN npm ci
COPY . .
RUN npm run build
# 运行阶段
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*. ./
RUN npm ci --only=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
CMD ["node_modules/.bin/next", "start"]这样构建出来的镜像只包含运行时需要的文件,next start 的资源占用只有开发模式的五分之一到十分之一。
第五步:还有一些进阶招数
用 nice 降低进程优先级
nice -n 10 npm run dev这样即使进程跑满了,也不会抢占系统关键进程的资源。
用 pm2 管理进程
// pm2.config.js
module.exports = {
apps: [{
name: 'next-dev',
script: 'node_modules/.bin/next',
args: 'dev',
instances: 1,
max_memory_restart: '500M',
env: {
NODE_ENV: 'development',
WATCHPACK_POLLING: 'false'
}
}]
};给宿主机增加 Swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile当内存不够的时候,系统会把不活跃的数据换到 Swap 里,虽然会慢一点,但至少不会直接崩溃。
最后给个建议操作顺序
1. 先用 docker stats 看看资源占用情况
2. 给容器加上 --cpus 和 --memory 限制
3. 如果是 Next.js 13+,试试 Turbopack
4. 优化 Next.js 配置,关掉不必要的功能
5. 实在不行就用生产模式,next build && next start
6. 可以的话,把开发服务放在宿主机或本地虚拟机里,Docker 里只跑生产服务
最后说两句
其实最省事的做法就是:开发和生产分离。本地开发就用宿主机跑 next dev,想怎么折腾都行;需要部署的时候,用 Docker 跑生产模式的镜像,资源占用低,稳定性也好。这样既不用担心宿主机被卡死,开发体验也不会差。
希望这些方法能帮到你。如果你还有其他问题,欢迎在评论区交流~