DevOps

Ngatasin Next.js Docker Build OOM Kill di CI

Asep Alazhari

Build Next.js yang lancar di laptop bisa tiba-tiba kena OOM kill di CI. Ini cara diagnosis dan fix limit memory Docker, jumlah CPU build worker, sama Node engine check.

Ngatasin Next.js Docker Build OOM Kill di CI

Ngatasin Next.js Docker Build OOM Kill di CI

Build-nya lancar jaya di laptop gue. Lancar juga sehari sebelumnya di runner CI yang sama. Terus tiba-tiba suatu pagi pipeline-nya berhenti di tengah jalan pas next build, gak ada error, gak ada stack trace, runner-nya cuma diem terus job-nya failed setelah timeout. Di titik itu lo baru sadar, CI host yang resource-nya terbatas itu bukan versi kecil dari mesin dev lo. Dia environment yang beda sama sekali, dengan failure mode-nya sendiri, dan Docker build Next.js dengan senang hati bakal ngebongkar semua itu satu-satu.

Gue habisin sore yang bikin frustrasi buat nelusurin ini sebelum akhirnya sadar, ini bukan satu bug. Ini tiga constraint terpisah yang numpuk bareng, dan dari luar ketiganya keliatan hampir sama persis.

TL;DR: Poin Penting

  • Build Next.js yang kena OOM kill atau hang di CI biasanya dari salah satu dari tiga penyebab ini: Docker build memory yang gak dibatasi, terlalu banyak parallel build worker buat jumlah CPU yang tersedia, atau Node engine check yang gak ada hubungannya malah ngeblok satu stage pipeline.
  • Diagnosis dulu pakai next build --experimental-debug-memory-usage (udah ada sejak Next.js 14.2.0) sebelum asal nebak fix-nya.
  • Batasin Docker build memory pakai --memory, batasin parallelism build Next.js pakai experimental.cpus di next.config.js, dan atasin engine check secara terpisah dari dua hal itu.
  • Next.js sekarang udah dokumentasiin pola ini resmi di panduan Memory Usage mereka, jadi ini konfigurasi yang didukung, bukan workaround aja.

Kenapa Build Next.js Lancar di Laptop Tapi Mati di CI?

Build Next.js lancar di laptop karena laptop lo punya resource yang gak perlu dia bagi ke siapa-siapa. CI runner biasanya dibatasin di 2 sampai 4 CPU dan memory ceiling yang fix, sering kali dishare sama job lain di host yang sama.

next build ngejalanin production compile lewat webpack atau Turbopack, type check seluruh project lo, dan defaultnya nyoba pake semua core CPU yang dia liat buat paralelisasi kerjaan. Di laptop dengan 8 atau 10 core dan RAM 16 sampai 32 GB, paralelisasi itu praktis gratis. Di CI runner yang cuma lapor 2 core dan RAM 4 GB, build yang sama tetep nyoba spin up jumlah worker yang sama, dan tiap worker megang chunk memory-nya sendiri. Build-nya gak gagal dengan mulus. Out-of-memory killer di kernel langsung matiin prosesnya, dan tergantung setup CI lo, yang lo dapet cuma exit code polos tanpa penjelasan.

Docker nambahin satu layer masalah lagi. Kalau lo build image di dalam step docker build di CI tanpa limit memory eksplisit, proses build-nya ngewarisin apapun yang diizinin sama container runtime, yang bisa jauh lebih kecil dari apa yang diasumsikan next build. Dua ceiling yang beda, dua titik gagal yang beda, tapi keliatan kayak build yang sama-sama stuck kalau dilihat dari luar.

Gimana Cara Diagnosis Resource Mana yang Sebenernya Bermasalah?

Jalanin build pake flag memory debug bawaan Next.js dulu sebelum lo ubah apapun. Sejak Next.js 14.2.0, lo bisa tambahin --experimental-debug-memory-usage buat print statistik memory secara live sepanjang proses build:

next build --experimental-debug-memory-usage

Ini nge-print penggunaan heap secara berkala selama build jalan, jadi lo bisa liat apakah memory-nya naik terus sampai runner-nya matiin, atau build-nya cuma hang aja tanpa ada kenaikan memory, yang berarti masalahnya di CPU atau scheduling, bukan memory. Gabungin ini sama resource graph bawaan platform CI lo buat job tersebut. Kalau memory-nya flat deket limit container pas job-nya mati, itu tandanya OOM kill. Kalau CPU usage-nya mentok di 100 persen tanpa progress di log, itu tandanya paralelisasi lagi berebutan core yang gak cukup.

Baru setelah itu lo tau fix mana dari tiga di bawah yang beneran cocok. Apply ketiganya sekaligus asal-asalan sih tetep jalan, tapi itu nutupin constraint mana yang beneran jadi masalah, dan lo gak bakal tau knob mana yang harus diputer lagi kalau ukuran runner CI lo berubah nanti.

Fix 1: Batasin Docker Build Memory Secara Eksplisit

Jangan biarin step docker build nganggep memory host-nya unlimited. Set ceiling eksplisit yang sesuai sama resource CI runner lo, plus headroom buat daemon-nya sendiri:

docker build --memory 8g --memory-swap 8g -t myapp:latest .

Set --memory-swap sama dengan --memory bikin swap dimatiin buat build itu, dan itu emang yang lo mau di CI. Build yang diem-diem swap ke disk alih-alih fail cepet cuma ngubah OOM kill jadi build yang makan waktu dua puluh menit alih-alih dua menit, yang sebenernya lebih parah karena ngabisin budget waktu pipeline lo tanpa ngasih tau ada yang salah.

Kalau platform CI lo build lewat Docker Buildx atau managed builder, cek juga setting alokasi memory-nya sendiri. Total memory runner sama memory yang dialokasiin Buildx buat step build itu gak selalu angka yang sama.

Fix 2: Batasin Parallelism Build Next.js Sesuai CPU yang Tersedia

Ini fix yang sekarang didokumentasiin langsung sama Next.js. Tambahin experimental.cpus di next.config.js dan set ke jumlah core yang beneran dilaporin CI host lo, bukan jumlah core laptop lo:

// next.config.js
const nextConfig = {
    experimental: {
        cpus: 2,
        webpackMemoryOptimizations: true,
    },
};

module.exports = nextConfig;

experimental.cpus ngebatasin berapa banyak parallel worker yang di-spin up Next.js buat compilation webpack, yang langsung ngebatasin berapa banyak memory yang bisa diklaim build itu sekaligus. webpackMemoryOptimizations, yang ada sejak Next.js 15.0, bikin perubahan tambahan di behavior internal webpack khusus buat ngurangin peak memory usage selama build. Keduanya didokumentasiin resmi di panduan Memory Usage, yang ditambahin Vercel karena pola failure ini emang cukup umum sampe butuh jawaban resmi, bukan cuma workaround yang direinvent tiap orang.

Lo bisa scope ini pake environment variable, jadi build lokal tetep pake semua core yang lo punya, sementara build CI tetep dibatasin:

const nextConfig = {
    experimental: {
        cpus: process.env.CI ? 2 : undefined,
    },
};

Baca Juga: Docker vs PM2 untuk Next.js di VPS: Panduan Deploy 2026

Kapan Node Engine Check Sendiri yang Jadi Penghalang?

Gak semua pipeline yang stuck itu masalah memory atau CPU. Satu stage terpisah di pipeline yang sama, dalam kasus gue itu stage test coverage, gagal total di host yang resource-nya terbatas karena engine check ketat dari package manager-nya nolak versi Node yang keinstall di image runner itu, padahal mismatch versinya sama sekali gak ada hubungannya sama test run yang aktual.

Yarn Classic dan npm sama-sama baca field engines di package.json dan bisa hard fail install atau script kalau versi Node host-nya gak cocok. Di CI, di mana versi Node image runner-nya dikontrol terpisah dari project lo, check itu bisa ngeblok satu stage karena alasan yang gak ada hubungannya sama kode lo. Lo bisa longgarin itu khusus buat stage tertentu tanpa perlu longgarin di mana-mana:

# Yarn Classic
yarn install --ignore-engines

# atau discope khusus buat job CI itu aja
YARN_IGNORE_ENGINES=true yarn test:coverage
# .npmrc, versi npm
engine-strict=false

Perlakukan ini sebagai pengecualian yang tertarget buat satu stage CI spesifik, bukan kebijakan blanket. Engine check itu ada buat nangkep incompatibility yang beneran, dan matiin dia secara global cuma nuker satu jenis silent failure dengan jenis lainnya.

Baca Juga: Kenapa Docker Buildx Mengubah CI/CD Gue Selamanya

Apakah Fix Ini Masih Direkomendasiin di 2026?

Iya. Per Next.js 15 dan dibawa terus ke Next.js 16, experimental.cpus sama experimental.webpackMemoryOptimizations berdua didokumentasiin langsung sama tim Next.js sebagai mitigasi resmi buat build memory pressure, bukan workaround komunitas yang diambil dari thread GitHub issue. Flag diagnostic --experimental-debug-memory-usage udah stabil sejak 14.2.0. Kalau lo masih pake versi Next.js lama yang belum punya opsi-opsi ini, upgrade dulu deh sebelum lo buang lebih banyak waktu ngoprek infra CI buat masalah yang sekarang udah ada jawaban resminya dari framework-nya sendiri.

Nyusun Build yang Aman Buat CI

Ini gimana ketiga fix di atas kalau digabung di satu job GitLab CI yang ngebuild image Docker Next.js di runner yang terbatas:

build-image:
    stage: build
    image: docker:26
    services:
        - docker:dind
    variables:
        YARN_IGNORE_ENGINES: "true"
    script:
        - docker build
          --memory 8g
          --memory-swap 8g
          -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
          .
        - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

Dengan next.config.js yang ngebatasin experimental.cpus di dalam build image itu sendiri, ketiga constraint-nya ditangani di layer masing-masing tempat mereka beneran kejadian, memory di level Docker, parallelism CPU di level Next.js, dan engine check yang discope cuma ke satu script yang butuh itu.

Pertanyaan yang Sering Diajukan

Kenapa build Next.js gue hang alih-alih nunjukin error di CI? Pas out-of-memory killer matiin proses build, dia sering ngelakuin itu tanpa ngasih kesempatan Node buat print stack trace, jadi job-nya cuma berhenti dan akhirnya timeout. Jalanin pake --experimental-debug-memory-usage buat liat memory-nya naik sebelum kematian itu, alih-alih nebak-nebak belakangan.

Apa itu experimental.cpus di Next.js? Itu opsi next.config.js yang ngebatasin berapa banyak parallel worker yang dipake Next.js selama step compilation webpack di next build, yang ngurangin peak memory usage di host dengan CPU core lebih sedikit dari mesin lokal lo.

Flag --memory di Docker ngebatasin seluruh build atau cuma container yang jalan? Dia ngebatasin proses build itu sendiri kalau di-pass ke docker build, bukan cuma container final pas runtime. Pasangin sama --memory-swap dengan nilai yang sama biar build-nya gak diem-diem swap ke disk alih-alih fail cepet.

Perlu gak gue matiin Node engine check di semua tempat biar CI gak gagal? Jangan. Scope ke stage CI spesifik yang butuh itu aja, pake flag kayak --ignore-engines punya Yarn atau environment variable khusus buat job itu aja. Bypass global nutupin incompatibility versi Node yang beneran, yang emang jadi alasan check itu ada.

Apakah webpackMemoryOptimizations aktif secara default di Next.js? Enggak, itu flag opt-in di bawah experimental sejak Next.js 15.0. Lo harus tambahin secara eksplisit di next.config.js bareng experimental.cpus kalau mau dua-duanya sekaligus buat ngurangin memory.

Kesimpulan

CI build yang gagal di host dengan resource terbatas itu bukan satu bug yang nyamar jadi tiga. Itu tiga ceiling terpisah, memory Docker, parallelism build Next.js, dan engine check package manager, yang kebetulan ngasih gejala yang sama: build yang stuck atau mati tanpa penjelasan. Diagnosis dulu pake memory debug flag, apply fix yang beneran cocok sama apa yang lo liat, dan anggep angka CPU sama memory yang beneran dilaporin CI runner lo sebagai source of truth, bukan apa yang bisa lolos di laptop lo.

Back to Blog

Related Posts

View All Posts »