Why we run Nitro on Bun in production
AiChat's server runs on Bun in production — nitro.preset: 'bun', so the process is
Bun.serve. The interesting part wasn't the runtime. It was the build.
The build doesn't run on Bun — on purpose
The Nitro server bundle (Rollup) is a known Nuxt memory hog — it peaks around 7.5 GB
regardless of runtime. DigitalOcean App Platform hard-caps every build at 8 GB,
un-raisable. Node's --max-old-space-size throttles the build heap so it fits under
that cap; Bun's JS engine has no equivalent throttle, peaks higher, and risks an
exit-137 OOM on the builder.
So the Dockerfile builds on Node and runs on Bun. The build engine doesn't change
the artifact — preset: 'bun' emits the same Bun.serve output either way — so the
production server is still 100% Bun.
The lesson
"Full Bun" is a runtime decision, not a build one. Match the tool to the constraint: Bun where it wins (the server), Node where the platform forces your hand (a memory- capped builder). The user never sees the difference.
If we ever outgrow the 8 GB build cap, the escape hatch is building the image in CI and deploying from a registry — which sidesteps the cap entirely.