Render Tutorials
When deploys go wrong

Build-time failures

⏱ 10 min

The build is where most first-time deploys die, and it’s also the easiest phase to diagnose because every clue is in one place: the deploy log. If you’ve internalised the method (step 02) and the logs cheat sheet (step 03), build failures are mostly pattern matching.

This step is the pattern library. Each section has the log line you’ll see, what it means, the diagnosis, and the smallest fix that disproves the hypothesis.

The diagnosis flow

flowchart TB
  start["Build failed"]
  q1{"First error<br/>shape?"}

  q1 -->|"Module not found /<br/>Cannot find package"| A["Section 1:<br/>Missing dependency"]
  q1 -->|"SyntaxError /<br/>incompatible engine"| B["Section 2:<br/>Language version"]
  q1 -->|"Lockfile out of date /<br/>frozen-lockfile failure"| C["Section 3:<br/>Lockfile drift"]
  q1 -->|"command not found /<br/>permission denied"| D["Section 4:<br/>Build command / tooling"]
  q1 -->|"OOM / killed during build"| E["Section 5:<br/>Build resource limits"]

Find the section that matches your first error and jump to it. The patterns at the end of the step are the rarer cases.

1. Module not found / ModuleNotFoundError

The single most common build failure across every language.

What you’ll see

Node deploy log
==> Running build command 'npm ci && npm run build'
> app@1.0.0 build
> next build
Creating an optimized production build...
./src/lib/auth.ts
Module not found: Can't resolve '@/lib/db'
==> Build failed
Python deploy log
==> Running build command 'pip install -r requirements.txt && python manage.py collectstatic'
Successfully installed Django-5.0.0 ...
==> Running pre-deploy command
Traceback (most recent call last):
ModuleNotFoundError: No module named 'redis'
==> Pre-deploy failed

What it means

One of two things, and the distinction matters:

VariantCauseWhere the cause is
Dependency missingA package referenced by your code isn’t in package.json / requirements.txtYour manifest file
Path resolution failureA relative or aliased path doesn’t resolve at build time on LinuxThe import statement or your bundler config

Diagnosis

Check whether the missing module is a third-party package or one of your own files.

  • Third-party (redis, lodash, psycopg2) → it’s not declared as a dependency. Run npm ls redis or pip show redis locally. If it’s not there, add it.
  • Your own file (@/lib/db, ./components/foo) → it’s a path issue. The most common cause is filesystem case: macOS and Windows treat ./Foo and ./foo the same; Linux doesn’t.

The fix

Terminal (dependency missing)
# Node
npm install --save the-missing-package
# Python
pip install the-missing-package
pip freeze > requirements.txt

For path issues, the smallest test is to rename the file with git mv to lowercase and reimport:

Terminal (case-sensitive filesystem)
# DON'T just rename on macOS - git won't see the change
git mv src/components/Foo.tsx src/components/Foo.tsx.tmp
git mv src/components/Foo.tsx.tmp src/components/foo.tsx
# Then update imports

2. Language / dependency version conflicts

The second-most-common, and the one that produces the most confusing error messages.

What you’ll see

Node: syntax error from an unsupported operator
> app@1.0.0 build
SyntaxError: Unexpected token '??='
at internalCompileFunction (node:internal/vm:73:18)
==> Build failed
Node: incompatible engine
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: 'some-pkg@5.0.0',
npm warn EBADENGINE required: { node: '>=20.0.0' },
npm warn EBADENGINE current: { node: 'v18.20.0' }
Python: incompatible dependency
ERROR: Package 'some-pkg' requires Python >=3.11, but the running Python is 3.10.13.
Go: module requires newer Go
go: go.mod requires go >= 1.22 (running go 1.21.5)

What it means

Render’s runtime is using a different language version than your project expects. The “default” version Render picks is documented in language-support, and it drifts over time - what was a fresh default when you set up the service may be older than your dependencies need today.

Diagnosis

Look at the first three lines of any deploy log:

==> Using Node version 18.20.0 (default)
==> Using Python version 3.10.13 (default)
==> Using Go version 1.21.5 (default)

The word (default) is the key signal. If it says (default), you haven’t pinned a version and Render picked one. Compare that against your project’s expected version:

Source of truthLives in
Nodeengines.node in package.json, .nvmrc, or .node-version
Pythonruntime.txt (e.g. python-3.12.1) or a .python-version file
Ruby.ruby-version or Gemfile
Gogo.mod (the go directive)

If the project’s expected version differs from what Render is using, you have your cause.

The fix

The cleanest fix is to set the version in a file your project already reads, so the same version is used locally and on Render. Pick one mechanism per language:

.nvmrc (Node)
20.11.0
runtime.txt (Python)
python-3.12.1

If you can’t use a project file (e.g. shared monorepo), set the environment variable in the Render Dashboard or a Blueprint:

render.yaml
services:
- type: web
name: api
runtime: node
envVars:
- key: NODE_VERSION
value: "20.11.0"

Available env vars per runtime: NODE_VERSION, PYTHON_VERSION, RUBY_VERSION, GO_VERSION, RUST_VERSION. See Render’s language-support docs for the full table.

3. Lockfile drift

You ran npm install locally, committed the lockfile, but the build still complains. Or your build uses npm ci and explodes.

What you’ll see

npm ci with drifted lockfile
npm error code EUSAGE
npm error
npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync.
npm error Invalid: lock file's express@4.18.2 does not satisfy express@4.19.0
pnpm with frozen lockfile
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json
pip with hash mismatch
ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.

What it means

Your package.json (or requirements.txt) has been edited but the lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock, Pipfile.lock) hasn’t been regenerated. npm ci, pnpm install --frozen-lockfile, and pip install --require-hashes are strict by design - they refuse to install if the two files don’t agree.

Diagnosis

Look at the date of your last lockfile change vs your last manifest change:

Terminal (local)
git log -1 --format="%ai %h" -- package-lock.json
git log -1 --format="%ai %h" -- package.json

If package.json is newer than the lockfile, that’s the drift.

The fix

Regenerate the lockfile locally and commit it:

Terminal
# Node
rm package-lock.json
npm install
git add package-lock.json
git commit -m "Refresh lockfile"
# pnpm
pnpm install
git add pnpm-lock.yaml
git commit -m "Refresh lockfile"
# Python with pip-tools
pip-compile requirements.in --output-file requirements.txt
git add requirements.txt
git commit -m "Refresh pinned requirements"

4. Build command / tooling issues

Sometimes the build command itself is the problem.

What you’ll see

Command not on PATH
==> Running build command 'pnpm install && pnpm build'
/bin/sh: pnpm: not found
==> Build failed
Permission denied
==> Running build command './scripts/build.sh'
/bin/sh: ./scripts/build.sh: Permission denied
==> Build failed
Empty / missing build command
==> Running build command ''
==> Build successful
==> Deploying...
==> No start command specified

What it means and what to do

SymptomCauseFix
pnpm: not found, yarn: not foundRender’s native runtimes default to npm. pnpm/yarn need to be installed firstPrefix the build command: corepack enable && pnpm install && pnpm build
Permission denied on a .sh fileThe script isn’t executablegit update-index --chmod=+x scripts/build.sh && git commit
Empty build command outputThe Render Dashboard’s build command field was clearedSet it back, or define it in render.yaml
pip-installer not foundBuild is using a base image that doesn’t have your toolchainSwitch to Docker or use Render’s native runtime for that language

For non-Node/Python tooling (Bun, Deno, custom binaries), the native-runtimes tools docs list what’s pre-installed. Anything not on that list, install it as part of your build command:

Build command for a Bun project
curl -fsSL https://bun.sh/install | bash && ~/.bun/bin/bun install && ~/.bun/bin/bun run build

Or move to a Dockerfile - at that point you’re managing the toolchain yourself.

5. Build resource limits

Rarer, but worth knowing about because it’s the most confusing failure when you hit it.

What you’ll see

OOM during build
==> Running build command 'npm run build'
> next build
Linting and checking validity of types ...
Creating an optimized production build ...
Killed
==> Build failed

Or the build just hangs, then:

==> Build exceeded maximum allowed time
==> Build failed

What it means

Builds run on a build worker with finite RAM and a time limit. Heavy builds - large TypeScript projects, big bundlers, image processing in the build step - can hit either limit.

Diagnosis

A Killed with no stack trace, especially from next build, vite build, or webpack, is almost always OOM. Confirm by reproducing locally with NODE_OPTIONS="--max-old-space-size=2048" to constrain to a similar memory ceiling.

The fix

A few levers, in order of effort:

  1. Skip what doesn't need to be in the build Move type-checking out of next build (SKIP_ENV_VALIDATION=1, NEXT_DISABLE_ESLINT=1, etc.). Run it in CI instead, before deploy.
  2. Cache aggressively Render caches node_modules between builds. Make sure you’re not invalidating it (changing the lockfile every deploy will).
  3. Move to a Dockerfile Multi-stage builds let you push more work into the build, but you also pay for the build environment yourself. Worth it for large projects.
  4. Contact support if you genuinely need more If you’ve optimised and still can’t fit, support can advise on plan options.

Two patterns that look like build failures but aren’t

”Build succeeded but my code didn’t update”

Suspicious deploy log
==> Cloning from https://github.com/example/app
==> Checking out commit 4f2a91c in branch main
==> Build successful

The commit being checked out is the third line. If it’s not the commit you expected, the build did exactly what you asked - Render is just deploying an older commit. Causes:

  • The deploy was triggered manually for a specific commit (deploy hook, API call, the Render Dashboard “Deploy specific commit” action).
  • Auto-deploy is off and a teammate pushed without triggering a deploy.
  • You pushed to a branch that isn’t the service’s deploy branch.

Fix is to trigger a fresh deploy or push the right commit to the right branch.

”Build is succeeding but the wrong service is being built”

In a monorepo, rootDir controls which subdirectory Render builds. If it’s wrong, you’ll build the wrong project and deploy “succeeds” with the wrong artifact.

render.yaml
services:
- type: web
name: api
rootDir: ./apps/api
runtime: node
buildCommand: npm ci && npm run build

Check rootDir in the Render Dashboard’s Settings → Build & Deploy section. The path is relative to the repo root.

The first three lines of your deploy log say: `==> Using Node version 18.20.0 (default)`. Your `package.json` has `"engines": { "node": ">=20.0.0" }` and the build is failing with `SyntaxError: Unexpected token '??='`. The smallest fix that disproves the hypothesis 'this is a version mismatch' is...

What you learned

  • `Module not found` is either a missing dependency (fix the manifest) or a path/case issue (fix the filename on a case-sensitive filesystem)
  • Language version mismatches show up as `SyntaxError`, `EBADENGINE`, or `requires Python >=`. The `(default)` marker in the deploy log's first lines tells you Render picked a version for you
  • Lockfile drift is what `npm ci` / `--frozen-lockfile` is *supposed* to catch. Fix the drift at the source by regenerating the lockfile, not by switching to `npm install`
  • `pnpm: not found` and similar mean the tool isn't in Render's native runtime - install it in the build command or use Docker
  • A `Killed` with no stack trace in a heavy build is almost always OOM. Optimise the build (skip type-checking, cache deps) or move to a Dockerfile
  • If the deploy log shows the wrong commit or the wrong service was built, the fix is in the deploy trigger or `rootDir`, not the code