Heap out of memory, Node.js 메모리, --max-old-space-size | 빌드가 137로 죽으면?
빌드 종료 코드 137은 문법 오류가 아니라 커널이 프로세스를 죽인 신호다. 힙 메시지와 러너 메모리를 같이 보고, 원본 이미지를 줄인 뒤 공식 힙 상한만 올린다. 한국 1인 개발자, Node.js, Next.js, 깃허브 액션, 도커, CI, 빌드 기준. 2026년 8월 Node.js와 Next.js, 깃허브 공식 문서.
128+9, 즉 SIGKILL을 받았다는 숫자입니다. 노드가 예외를 던지고 스스로 끝난 0이나 1과 다릅니다. 리눅스 OOM killer, 도커 메모리 한도, 깃허브 액션 러너 한도가 이 신호를 보냅니다.
힙 부족이면 그 전에 FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory가 로그에 남습니다. 137만 있고 힙 메시지가 없으면 다른 프로세스가 잡혔거나, 로그가 잘린 겁니다. CI 로그 전체를 내려받으세요.
윈도 작업 관리자에서 종료하면 숫자가 다릅니다. 이 글은 리눅스 CI·도커·버셀 빌드처럼 유닉스 종료 코드를 쓰는 칸만 봅니다.
먼저 기억할 것 | 137은 힙 플래그 한 줄로 끝나지 않습니다. 러너 RAM이 3GB인데 힙을 8GB로 올리면 같은 신호로 더 빨리 죽습니다. 한도와 플래그를 같이 보세요.
힙 상한을 올려도 컨테이너 RAM이 더 작으면 137로 죽는다
힙 플래그와 러너 RAM을 같이 보기
플래그는 V8 구세대 힙 상한이고, 러너 RAM은 그 상한의 천장입니다.
칸
무엇을 제한하나
올리는 방법
V8 힙
자바스크립트 객체 공간
--max-old-space-size=4096
NODE_OPTIONS
자식 노드에도 같은 플래그
환경변수로 전달
러너/도커 RAM
프로세스 전체 RSS
larger runner, mem_limit
스왑
RAM을 넘긴 뒤 디스크
느리고 OOM을 늦출 뿐
넥스트 빌드
페이지·이미지 동시 처리
실험적 메모리 옵션, 이미지 축소
공식 CLI는 메가바이트 정수입니다. 존재하지 않는 플래그를 넣지 마세요. NODE_OPTIONS=--max-old-space-size=4096는 npm 스크립트가 띄운 노드에도 전달됩니다.
깃허브 호스티드 러너는 당시 문서의 RAM을 따릅니다. 큰 모노레포면 larger runner나 셀프 호스티드가 필요합니다. 힙만 키우고 러너는 그대로면 137이 남습니다.
힙 상한을 환경변수로 주기
export NODE_OPTIONS=--max-old-space-size=4096
npx next build
# package.json
# "build": "NODE_OPTIONS=--max-old-space-size=4096 next build"
올리기 전에 빌드가 먹는 양을 줄이기
원본 이미지를 public에 통째로 두면 빌드가 한꺼번에 디코드합니다. 넥스트 이미지 최적화는 편리하지만 CI RAM을 많이 씁니다. 큰 PNG를 미리 줄이거나, 빌드 중 처리할 이미지 수를 나누세요.
소스맵을 프로덕션 빌드에서 켜 두면 힙이 늘어납니다. 디버그가 끝나면 끕니다. 웹팩 캐시가 깨진 채 커지면 한 번 .next를 지우고 다시 빌드해 보세요. 로컬 캐시가 CI에 없으면 CI만 더 먹기도 합니다.
타입스크립트 incremental과 린트를 빌드 잡에서 분리하면 피크가 낮아집니다. 타입체크 잡을 따로 두고 빌드 잡의 메모리를 빌드에만 쓰는 식입니다. 한국에서 혼자 돌리는 소형 사이트는 이 분리만으로 무료 러너에 남는 경우가 있습니다.
빌드와 타입체크를 나누기
# CI 잡 1
npx tsc --noEmit
# CI 잡 2
NODE_OPTIONS=--max-old-space-size=4096 npx next build
[ ] 로그에 heap out of memory가 있는지 확인했다
[ ] 종료 코드가 137인지 1인지 구분했다
[ ] 러너/컨테이너 RAM과 힙 상한을 비교했다
[ ] public 원본 이미지 용량을 줄였다
[ ] NODE_OPTIONS를 공식 플래그만으로 넣었다
큰 원본 이미지를 빌드가 한꺼번에 열면 힙이 먼저 찬다
로컬은 되고 CI만 죽을 때
노트북 RAM이 러너보다 큽니다. 맥 16GB에서 되던 빌드가 7GB 러너에서 137이 납니다. 로컬에서 NODE_OPTIONS=--max-old-space-size=2048로 한도를 낮춰 재현하면 CI와 비슷해집니다.
스왑을 CI에 넣는 방법은 느리고, 호스티드 러너에선 막혀 있을 수 있습니다. 의존하지 마세요. 셀프 호스티드라면 스왑은 최후입니다.
버셀 빌드는 플랫폼 한도가 따로 있습니다. 로컬 next build와 클라우드 빌드 머신 사양이 다릅니다. 타임아웃은 다른 글이고, 여기서는 메모리 부족만 봅니다.
확인되지 않은 플래그 금지 | 블로그에 떠도는 --optimize-for-size 조합을 그대로 붙이지 마세요. Node.js CLI 문서에 있는 --max-old-space-size와 NODE_OPTIONS만 씁니다.
문법 에러 로그와 섞지 않기
힙 부족은 스택 한 줄이 아니라 FATAL ERROR 블록입니다. ESLint 실패나 타입 에러는 종료 코드 1에 파일 경로가 있습니다. 둘을 한 커밋에서 고치려다 플래그만 올리면 타입 에러가 남습니다.
모듈을 못 찾아 빌드가 일찍 끝나면 메모리를 쓰기 전입니다. 그 줄은 Cannot find module입니다. npm 스크립트가 1로 끝나면 ELIFECYCLE이 따라붙습니다. 137은 그 위 단계입니다.
제가 돌리는 사이트 빌드도 이미지 폴더가 커진 주에 통합 환경만 죽었습니다. 원본을 줄인 뒤 힙 숫자는 4096에 멈췄습니다. 더 올리지 않은 이유는 러너 천장 때문이었습니다. 혼자 운영하면 노트북이 넉넉해 원인을 늦게 알아챕니다. 배포 쪽 기계를 기준으로 한 번 재현해 보는 버릇을 들이는 편이 낫습니다.