Docs menu

Troubleshooting Node.js

Common build and runtime errors for Node.js apps on Primcloud, with the fix for each.

On this page

Two places to look when a deploy misbehaves:

  • Build log: the Deployments tab streams the full output from the build. Node.js version resolution, package manager install, build scripts, and dev-dependency pruning all surface here.
  • Runtime log: the Logs tab streams stdout and stderr from your running pods. Startup crashes, unhandled promise rejections, and native-load failures show up here.

This page lists the errors that come up most often, in the order they'd typically bite you: detection, runtime resolution, package install, build scripts, then runtime.

Common build errors#

No Node.js application found#

The build looked for a package.json at the repo root and didn't find one.

Fix. Move the package.json to the repo root, or open a monorepo subdirectory as its own Primcloud app.

Unknown Node.js version#

The engines.node range in package.json doesn't match any version the Primcloud Runtime ships. Either the version isn't a real Node.js release, it's an unsupported major (21, 23, or older than what the Runtime still ships), or you pinned a patch that hasn't been added to the Runtime yet.

Fix. Bump to a version listed in Runtime versions, or loosen the range from an exact version to "24.x" so the resolver can pick a close match.

Node.js version is End-of-Life#

The build log prints a warning: Node.js <version> is now End-of-Life (EOL). The build still succeeds today, but in a future Runtime update this becomes a build error and the version is removed from the Runtime.

Fix. Bump engines.node to a supported major before the version is removed. See When a version drops out of support.

Missing lockfile / dependencies not installed#

The build log shows the runtime installing but no node_modules install step. Your app has a package.json but none of package-lock.json, pnpm-lock.yaml, or yarn.lock.

Fix. Run npm install, pnpm install, or yarn install locally, commit the generated lockfile, and redeploy. See Package managers → Lockfiles.

Wrong lockfile / package manager mismatch#

package.json declares "packageManager": "pnpm@..." (or engines.pnpm) but the repo ships a package-lock.json. The build tries to run pnpm and fails because pnpm-lock.yaml is missing, or worse, picks the wrong manager entirely.

Fix. Ship the lockfile matching your chosen manager, and delete lockfiles for other managers. Only one of package-lock.json, pnpm-lock.yaml, yarn.lock should exist in the repo.

npm ci / pnpm install / yarn install fails#

The lockfile disagrees with package.json. Common causes: a manual edit to package.json without regenerating the lockfile, a merge that resolved package.json but not the lockfile, or a dependency using a git or file URL that isn't reachable from the build environment.

Fix. Reproduce locally with the same command the build runs:

Terminal
npm ci
pnpm install --frozen-lockfile
yarn install --immutable   # Yarn 2+
yarn install --frozen-lockfile   # Yarn 1

If it fails locally with the same error, the fix is the same: regenerate the lockfile, address the version conflict, or make the dependency URL reachable. If it passes locally, check that the lockfile is actually committed (git status in a fresh clone).

Build script failed: heroku-prebuild / heroku-build / build / heroku-postbuild#

One of your build scripts exited non-zero. The script output is in the build log directly above Primcloud's own error message.

Fix. Read the script's stderr and address it. Reproduce locally with the same command:

Terminal
npm run build

Common causes: TypeScript compile errors, missing environment variables the build needs (set them in the Secrets panel), out-of-memory errors during a bundler run (see Debugging tips below).

Native module fails to compile: node-gyp errors#

An npm dependency with a native addon (sharp, bcrypt, sqlite3, canvas, etc.) failed to build. The node-gyp output above Primcloud's error usually names the missing header or library.

Fix. Two paths:

  • If the package publishes prebuilt binaries (most modern ones do) and the build is trying to compile from source anyway, check the package's install docs. You may need to set an env var like SHARP_IGNORE_GLOBAL_LIBVIPS=1 in the Secrets panel to force the prebuilt path.
  • If the package needs an OS-level library (libvips, libpq, ImageMagick), declare it in project.toml; see Customizing the build → Debian packages.

Cannot find module '...'#

A require() or import resolves to nothing.

Fix. Common causes:

  • The module is a devDependency and you install with pnpm or Yarn, so the build pruned it after the build scripts. Move it into dependencies. That's where every runtime import belongs. With npm, a devDependency stays installed.
  • The build script writes files that aren't checked into git (e.g. generated code) and no build step regenerates them on deploy. Add the generator to heroku-postbuild or build.
  • A relative import has the wrong case. macOS and Windows filesystems are case-insensitive; Linux isn't. import Foo from './foo' works locally and fails on Primcloud.

Common runtime errors#

App doesn't bind to $PORT#

Symptom: the deployment reaches Release Succeeded but the URL returns a connection error, and the runtime log has no "listening" line.

Fix. Every HTTP server on the web formation must listen on process.env.PORT. Primcloud sets it; hardcoded ports (3000, 8080) aren't reachable from the outside.

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`listening on ${port}`))

Bind to 0.0.0.0 or omit the host entirely. Never use 127.0.0.1 on the web formation.

App exits immediately on start#

The runtime log shows the process starting and exiting with no other output, or an uncaught exception right at startup.

Most common causes:

  • Missing required env var. If your app reads a database URL or API key from an env var and throws when it's missing, set the value in the Secrets panel and redeploy.
  • start script not defined. If there's no Procfile and no start script in package.json, Primcloud falls back to node index.js / node server.js. If neither of those files exists, the pod has nothing to run. Add a Procfile or a start script.
  • Wrong entry point. npm start runs whatever your start script says. Verify it points at the actual entry file, not a stale one.

Out of memory on start or during build#

The build log or runtime log shows FATAL ERROR: Reached heap limit Allocation failed.

Fix. Two axes:

  • At build time (typically during a bundler run): reduce parallelism (NODE_OPTIONS=--max-old-space-size=2048).
  • At runtime: move to a larger compute size, or set NODE_OPTIONS=--max-old-space-size=<mb> in the Secrets panel to give V8 a higher heap limit.

Native addon fails to load at runtime#

Error: Cannot find module '/app/node_modules/sharp/build/Release/sharp.node'

or

Error: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_...' not found

A native addon compiled successfully but can't load, often because the OS-level library it needs isn't present, or the addon was built for a different architecture.

Fix. Declare the missing library in project.toml. See Customizing the build → Debian packages.

Debugging tips#

  • Reproduce the install locally. npm ci, pnpm install --frozen-lockfile, or the Yarn equivalent. These are the exact commands the build runs. Most install failures reproduce this way.
  • Reproduce the build locally. npm run build runs your build script with the same env the build sees, minus the platform-set variables. If it works locally, the diff is usually an env var. Check what your build script reads from process.env and mirror those values.
  • Check the resolved Node.js version. The build log's Installing Node.js distribution section names the exact version installed. If it's not what you asked for, your engines.node range is looser than you thought. Tighten it.
  • Turn on richer logs temporarily. Set DEBUG=* (or a narrower namespace like DEBUG=express:*) in the Secrets panel to make debug-namespaced modules emit their trace output.
  • Peel apart install vs. build vs. start. When you don't know which phase is failing, the build log timestamps and phase headers (Installing Node.js, Installing node modules, Running scripts, Configuring default processes) tell you exactly where the last successful step was.