← Back to the library
OperationsCore · 7 min

Code works locally but fails in production

“Everything passes on a laptop. In production the same commit crashes at startup, cannot find a module, talks to the wrong API, or reads a config value as undefined.”

ERROR CODES YOU MAY SEE

A code is a clue. Use its meaning and surrounding evidence to narrow the cause.

ERR_MODULE_NOT_FOUNDNode.js errors · platform

The ECMAScript module loader could not resolve a file during an import or while loading the entry point.

If it fails only in Linux containers or CI, compare the import path's casing with the file on disk. macOS and Windows file systems are usually case-insensitive; Linux is not.

Official reference for ERR_MODULE_NOT_FOUND (opens in new tab)Code definition reviewed

THE PRINCIPLE

Build one artifact, inject config at runtime, and validate config at startup so a bad environment fails loudly at boot instead of halfway through a request.

FIRST MOVES

  1. Diff the effective config: print the variable names (not the values) the process sees in production next to your local .env. Look for typos, empty strings, and quoting.
  2. Find what is fixed at build time: NEXT_PUBLIC_*, import.meta.env.VITE_*, and values baked in through Docker build ARGs are fixed when you build, so promoting one image to production keeps the staging values.
  3. Run the production image locally with production-like env (docker run --env-file). Linux file systems are case-sensitive, so an import of ./Utils that matches utils.ts on macOS fails there.
  4. Check the architecture: an arm64 laptop and amd64 servers need multi-platform builds, and native modules must be installed inside the target image, not copied from the host.
  5. Compare runtime versions and NODE_ENV; devDependencies and dev-only fallbacks are missing in production installs.

TOOLS: THEN → NOW

process.env.X read wherever it is neededOne schema-validated config module loaded at startup (zod, envalid, pydantic-settings)
A separate build per environmentBuild once and promote the artifact, with runtime config for anything that differs between environments
Builds on the developer's machinedocker buildx build --platform linux/amd64,linux/arm64 in CI

PATTERN SNAPSHOT

// config.ts: imported first; the process refuses to start on bad env.
import { z } from "zod";

const Env = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]),
  DATABASE_URL: z.url(),
  PORT: z.coerce.number().int().positive().default(3000),
});

const parsed = Env.safeParse(process.env);
if (!parsed.success) {
  console.error(z.prettifyError(parsed.error)); // names and issues, not values
  process.exit(1);
}
export const config = parsed.data;

CLOSE THE AI. EXPLAIN THIS.

You promote the exact staging Docker image to production and the browser still calls the staging API. Why, and how do you prevent it?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed