2GBのVPSで next build は通った — ただしスワップに140MiB逃がしていた
2GBのVPSで、キャッシュを使わずに Next.js のビルドを1回だけ実行し、1秒ごとにメモリを記録しました。3分26秒で成功しましたが、その間にスワップが140MiB増えています。足りる・足りないの二択では説明できない結果でした。

通った。ぎりぎりで
Contents
2GBのVPSで next build を回して落ちないか、私はずっと不安でした。プランの数字を並べたページはいくらでも出てきますが、自分の構成で本当にどうなるのかは、これまで測っていませんでした。
測りました。落ちませんでした。ただ、記録を見ると「余裕でした」とも書きにくい結果です。
4GBとは比べていません。 比べられなかった理由は最後に書きます。
先に、結果だけ
63日連続で動いている本番のVPS(ConoHa の2GBプラン・3コア)で、ビルドキャッシュを使わずに Next.js のビルドを1回実行し、1秒ごとにメモリを記録しました。
ビルド docker compose build --no-cache frontend
終了コード 0(成功)
所要時間 3分26秒
available 1,048 → 470 MiB ビルド中の最小値。578MiB 減った
スワップ 313 → 453 MiB 140MiB 増えた(計測中の最大は 500 MiB)
ビルドキャッシュ 28.9 → 31.21 GB この1回で 2.31GB 増えた
サイト ビルド後に外から1度 HTTP 200 を確認(ビルド中は見ていない)
通りました。そして、通っている途中でスワップが140MiB増えています。
「2GBで足りるか、足りないか」という聞き方だと、この結果は答えにくいと思いました。足りています。同時に、一部をディスクへ逃がしてもいます。
何を、どう測ったか
このサーバーは記事02で46日目に測ったものと同じ箱です。Docker のコンテナが6つ動いています(Next.js / Django / PostgreSQL / nginx / certbot / redis)。
ビルドしたのはこのうち Next.js のフロントエンドだけで、中身はこうなっています。
要約すると、この3手です(実際のファイルには COPY などの行も入っています)。
FROM node:22-alpine → npm ci → npm run build
計測用のスクリプトを1つ動かし、その中でビルドを実行して、1秒ごとに free の値を記録しました。サンプルは205点です。
なぜキャッシュを使わなかったか
そのまま docker compose build を打つと、おそらく数秒で終わります。ビルドキャッシュが357エントリ・28.9GB 残っていて、8月13日に測ったときから357エントリ・28.9GB・回収可能26.02GB のどれも動いていないからです。ソースが変わっていなければ全部キャッシュに当たり、メモリはほとんど使われません。それでは測ったことになりません。
--no-cache を付けると npm ci からやり直します。これから借りる人が最初にやるデプロイと、同じ条件です。
本番のイメージを差し替えないようにした
もう1つ、注意した点があります。私の compose ファイルには image: の指定が無いので、ビルドすると、できたイメージに本番と同じ名前が付きます。動いているコンテナはそのままですが、あとで誰かが up -d を打った瞬間に、計測用のビルドがデプロイされてしまいます。
避けるには、プロジェクト名を捨て用のものに変えます。
docker compose -p measure-tmp -f docker-compose.yml -f docker-compose.prod.yml build --no-cache frontend
こうするとイメージ名が変わるので、本番のほうには触りません。この構成はビルドの引数に環境変数を参照していないため、プロジェクト名を変えてもビルドの中身と重さは同じです。終わったら消しました。
1回だけです
この計測は1回きりです。 3回まわして中央値を取る、といったことはしていません。理由は2つあって、片方は後の節に書きます。もう片方は単純に、本番のサーバーだからです。
ビルド前の状態
出発点の数字を置いておきます。2026年8月30日、ビルドを始める直前です。
- コンテナ6つの合計メモリ … 306.4 MiB(backend 147.4 / frontend 126.7 / certbot 12.13 / nginx 10.5 / postgres 7.848 / redis 1.824)
- メモリ … total 1,966 / used 917 / free 289 / buff-cache 1,021 / available 1,048(単位はMiB)
- スワップ … total 2,047 のうち 313 使用、1,734 空き(単位はMiB)
- uptime / load average … 63日 / 0.44, 0.24, 0.20
- ディスク … 99GB中 37GB 使用(40%)
記事02 で46日目に測ったときと、ほとんど同じです。コンテナの合計は 333MiB から 306.4MiB へ少し下がっていました。
引っかかるのはビルドキャッシュのほうで、357エントリ・28.9GB のまま動いていません。この17日間、サーバーの上で一度もビルドしていなかったということです。稼働だけしていた期間に余裕があったのは、当たり前でした。
ビルド中に動いた数字
| 見たもの | ビルド前 | ビルド中 | ビルド後 |
|---|---|---|---|
| available | 1,048 MiB | 470 MiB(最小) | — |
| スワップ使用量 | 313 MiB | 500 MiB(最大) | 453 MiB |
| ビルドキャッシュ | 28.9 GB / 357件 | — | 31.21 GB / 376件 |
available は578MiB 減って、470MiB で底を打ちました。2GBの箱に対して、残りは4分の1弱です。
スワップは140MiB 増えました。計測中に一度500MiB まで上がり、終わった時点で453MiB です。
「まだ470MiB 余っているのに、なぜスワップが増えるのか」と思いました。この問いの立て方のほうが間違っていました。
available は「空いているメモリ」ではありません。カーネルの公式ドキュメントは、MemAvailable をこう定義しています。
An estimate of how much memory is available for starting new applications, without swapping. Calculated from MemFree, SReclaimable, the size of the file LRU lists, and the low watermarks in each zone.
(Linux kernel documentation — proc.rst)
空きメモリそのものではなく、ファイルキャッシュと各ゾーンの下限値から計算した見積もりです。
そして回収は、メモリが尽きてから始まるわけでもありません。
As the load increases, the amount of the free pages goes down and when it reaches a certain threshold (low watermark), an allocation request will awaken the
kswapddaemon.
(Linux kernel documentation — concepts.rst)
同じ節は、回収の対象を "page cache and anonymous memory" としています。下限に触れた時点で回収が始まり、匿名メモリもその対象になる。
⚠️ ここで下限がかかっているのは available ではなく free です。 ビルド前の free は289MiB でした。available の1,048MiB とは別の数字です。空きが尽きる前にスワップが増えること自体は、仕組みとして起こりえます。
⚠️ ただし、今回それが起きたとは書けません。 このサーバーの vm.swappiness は測っていませんし、1秒ごとの free の値だけでは、どの順序で何が回収されたかまでは読み取れません。
言えるのは、ビルドを始める前より140MiB 多くのデータがディスク側に置かれた状態で、ビルドが終わったということだけです。
サイトについては、ビルドが終わったあとに外から1度 curl して、200 が返ることを確認しました。 ビルド中は見ていません。ビルドはイメージを作る作業なので動いているコンテナを止めませんが、止まらなかったことを連続で確認したわけではありません。
記事02 に残していた宿題
VPSは2GBで足りるかには、こう書いていました。
294MiB がスワップに逃がされていました。
常時動いているのが合計333MiBであることを考えると、これを押し出したのは普段の動作ではなさそうです。一時的に大きくメモリを使った処理があったのだろう、と考えました。
心当たりはビルドでした。そこまで書いて、その先は測っていませんでした。
今回、ビルド中にスワップが増えるところを見ました。ビルドがスワップを押し出すことは確かめられました。
⚠️ ただし、294MiB の全部がビルドによるものだったとは言えません。 この記事の数字がそれを示しています。
2026-08-13 スワップ 294 MiB
2026-08-30 スワップ 313 MiB(ビルド前)
この17日間、サーバーの上では一度もビルドしていません(上に書いたとおり、ビルドキャッシュが1件も動いていません)。それでも19MiB 増えています。 ビルドは押し出しますが、押し出しているのはビルドだけではありません。
そしてもう1つ。当時は同じ流れで、限界もビルド側に出るという書き方をしていました。今回の結果が裏づけたのは「負荷がビルドに出る」までで、「限界に届いた」ことは示していません。 届く手前で、スワップに逃がしながら通っています。記事02 のほうも、あわせて直しました。
ディスクのほうも、輪郭は見えました。キャッシュ無しのビルド1回で2.31GB 増えます。28.9GB は、こうした積み重ねでできたものです。⚠️ ただし毎回2.31GB ずつ増えるわけではありません(キャッシュに当たれば増えません)。何回ぶんかは数えていません。
測り方でつまずいた2つ
ピークRSS が 7MB と出た
/usr/bin/time -v を通してビルドを実行したので、ビルドが使ったメモリの最大値も取れるつもりでいました。出てきたのは 7,168 KB、つまり7MBです。Next.js のビルドが7MBで済むとは思えません。
測っていたのは docker compose を呼び出した側のプロセスでした。実際のビルドは Docker のビルドエンジンが別のプロセスで走らせるので、time から見える範囲の外にあります。
docker build を time で包んでも、ビルドプロセスのピークは取れないと考えたほうがよさそうです。取るなら、外から free を定期的に見るほうです。この記事の数字が全部 available とスワップになっているのは、そのためでした。
スワップの増分は、1回しか測れない
もう1つ。スワップの使用量は、メモリに余裕ができても自動では戻りません。
つまり2回目のビルドは、313MiB ではなく453MiB から始まります。同じ条件での再測定になりません。戻すにはスワップを一度切って入れ直す必要があり、2GBのサーバーでそれをやるのは、あまり気持ちのいい操作ではありません。
available の最小値のほうは、何度でも測れます。取り直せないのはスワップの増分だけです。「3回まわして中央値」という測り方は、この項目には使えませんでした。
この構成では、ビルドに上限が掛かっていなかった
計測の準備で設定を読んでいて、気づいたことがあります。ベースの docker-compose.yml の frontend には、Node のヒープ上限が書いてあります。
environment:
NODE_OPTIONS: --max-old-space-size=1536
書いてある場所は environment: の下、つまりコンテナを動かすときの環境変数としてです。build.args には同じ指定がありません。そしてビルドを走らせる RUN npm run build にも、上限は何も付いていません。
自分のファイルを読んだかぎり、この構成のビルドには上限が効いていない、ということになります。
一般論として「environment: に書いたものはビルドに渡らない」と言い切れるかどうかは、公式ドキュメントで確かめきれませんでした。Compose の仕様には environment を「コンテナに設定される環境変数」と定義した一文がありますが、ビルド時に渡らないと明示した記述は、私が見た範囲では見つけられていません。なのでここは、自分の構成についての観察に留めます。
そして今回は、上限を掛けないまま通りました。掛けたらどうなるかは測っていません。 上限を掛ければスワップが減るのか、それとも途中でビルドが落ちるのか、どちらも試していないので書けません。気になっている、というだけです。
測っていないこと
この記事で書けないことを並べておきます。
- 4GBならどうなるか。 ConoHa のプラン変更は、サーバーを停止しないとできません(4社の公式ドキュメントを読み比べた記事で確認しています)。本番を止められないので、比較していません
- 他社のVPSでどうなるか。 契約しているのは ConoHa の1社だけです
- バックエンド(Django)のビルド。 今回測ったのはフロントエンドだけです
- ヒープ上限を掛けた場合。 上の節のとおりです
npm ciとnext buildのどちらが重かったか。--no-cacheなので計測区間には両方が入っています。1秒ごとの記録はありますが、工程ごとには切り分けていません
記事02 で「サーバー上でビルドするなら4GB以上」と書いた部分は、いまも推定のままです。今回の実測は、それを裏づけても否定してもいません。2GBで1回通った、という事実が1つ増えただけです。
私の環境にあって、あなたの環境に無いかもしれないもの
同じ数字が出るかどうかは、次の条件に左右されます。この計測の前提として書いておきます。
- スワップが2,047MiB 確保されていて、1,734MiB 空いていました。 スワップが無い、または小さいサーバーだと、140MiB の逃がし先がありません
- ディスクの空きが58GB ありました。
--no-cacheのビルド1回で2.31GB 増えるので、残りが少ないと別の理由で止まります - ビルドしたのはフロントエンド1つです。同時に2つ以上ビルドすれば、当然もっと使います
- 動いているコンテナ6つの合計が306.4MiB でした。ここが重いと、出発点の available が違います
- 深夜で、アクセスがほとんど無い時間帯でした
- Node 22 の alpine イメージ、Next.js のビルドです。他のフレームワークの数字ではありません
で、サーバーでビルドするか、外でビルドするか
ここからは、上の数字をどう読むかという話です。
2GBのサーバーの上でビルドを回すこと自体は、私の構成ではできました。ただ、余裕は470MiB まで落ちます。ここに何かを足せば、その分だけ底が浅くなります。
- コンテナをもう1つ増やす
- バックエンドも同時にビルドする
- ビルド中にアクセスが来て、アプリ側がメモリを使う
どれも、やったら落ちるとまでは言えません。ただ、470MiB という残りは、こうしたものを何個か重ねるには薄いと思いました。
もう1つの道は、ビルドをサーバーの外でやることです。手元のPCや外部のサービスでビルドして、できたものだけをサーバーへ置きます。この場合、サーバーのメモリはビルドに1バイトも使いません。ただし何でも外に出せるわけではないので、その分かれ目は記事02の後半に書いたとおりです。
決めるときに先に見るのは、プランの数字よりも「どこでビルドするか」だと思います。そこが決まらないと、必要なメモリの見積もりが立ちません。
まとめ
- 2GBのVPSで、キャッシュ無しの Next.js ビルドは通った。3分26秒、終了コード 0
- ただし available は 1,048 → 470 MiB まで落ち、スワップが140MiB 増えた
- ⚠️ サイトはビルドの後に外から1度 HTTP 200 を確認しただけで、ビルド中は見ていない
- ディスクはこの1回で2.31GB 増えた。28.9GB のキャッシュは、こうした積み重ねでできている
- 1回だけの計測。スワップの増分は、条件をそろえた再測定ができない
- 4GBとの比較はしていない。プラン変更にサーバーの停止が要るため