5 min read

Docker 里跑多个 Next.js 开发服务把宿主机搞死了?这里有套救命方案

前两天在群里看到有同学吐槽:在 Docker 容器里跑几个 Next.js 开发服务,没过多久整个宿主机就卡死不动了,连 SSH 都连不上,只能强制重启。这事儿说实话我也踩过坑,今天就来说说为什么会这样,以及有哪些实用方案能帮你避开这个坑。

先搞清楚为什么会卡死

Next.js 的开发模式(就是那个 next dev)可不是个轻量级的货色。它背后干的事儿可多了:

  • 启动完整的 Webpack 编译器
  • 开启文件监听,实时检测代码变化
  • 运行热模块替换(HMR),一改代码就自动刷新浏览器
  • 维护一大堆缓存

这一套组合拳下来,CPU、内存、磁盘 I/O 都得被狠狠占用。你在同一台机器上跑多个这样的服务,资源需求就会成倍增长。结果就是:

  1. 内存被吃光 → 系统疯狂换页,响应变得奇慢无比,或者直接触发 OOM-Killer 杀进程
  2. CPU 被抢光 → 所有监听和编译进程在抢 CPU,系统调度器都来不及响应你的操作
  3. 磁盘 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 跑生产模式的镜像,资源占用低,稳定性也好。这样既不用担心宿主机被卡死,开发体验也不会差。

希望这些方法能帮到你。如果你还有其他问题,欢迎在评论区交流~