Mid-level 10 min · March 28, 2026
Nodemon: Auto-Restart Node.js Apps During Development

Nodemon Docker Mac: legacyWatch Fix for Stale Code

On Docker for Mac, Nodemon silently ignores file saves.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
May 24, 2026
last updated
1,663
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Nodemon watches file changes and restarts Node.js automatically in development
  • Install locally with --save-dev; never globally or in production
  • Use nodemon.json to control watch paths, ignore patterns, and debounce delay
  • The infinite restart loop happens when the app writes to watched dirs — fix with ignore
  • On Docker for Mac, file events may not propagate; enable legacyWatch or polling mode
  • For TypeScript, use exec key with tsx — 10x faster than ts-node for large codebases
✦ Definition~90s read
What is Nodemon?

Nodemon is a utility that monitors your Node.js application source files for changes and automatically restarts the process. It solves the friction of manually stopping and restarting your server after every edit — a workflow that kills focus and wastes time.

Imagine you're editing a Google Doc and every time you change a word, someone has to manually walk over, close the document, and reopen it before you can see the update.

Under the hood, Node.js itself has no built-in file-watching restart mechanism; nodemon wraps your node command, watches the filesystem via fs.watch (or chokidar for cross-platform reliability), and kills/re-spawns the process when it detects a change. It's not a build tool or a bundler — it's a development-time convenience layer that sits between you and node app.js.

In the ecosystem, nodemon competes with tools like ts-node-dev (TypeScript-specific), supervisor, and framework-specific watchers (Next.js, Remix). You should NOT use nodemon in production — it's strictly for local development. When you need to watch TypeScript, you'll typically pair it with ts-node or a compile step.

For Docker on Mac, nodemon's default polling mechanism can fail because macOS's filesystem events don't propagate correctly into Docker containers, causing stale code — hence the legacyWatch fix, which forces file polling at the cost of CPU.

Concretely, nodemon is installed as a dev dependency (npm i -D nodemon) and invoked via npx nodemon or an npm script. It supports config via nodemon.json, CLI flags, or package.json's nodemonConfig key. The --legacy-watch flag (or usePolling: true in config) is the specific workaround for Docker-on-Mac environments where inotify events don't cross the filesystem boundary.

Without it, you'll hit the infamous 'stale code' problem — editing a file on your Mac host does nothing inside the container until you manually restart.

Plain-English First

Imagine you're editing a Google Doc and every time you change a word, someone has to manually walk over, close the document, and reopen it before you can see the update. That's exactly what developing a Node.js app without Nodemon feels like — you change one line, then you Ctrl+C the server, retype 'node server.js', and wait. Nodemon is the person standing over your shoulder who sees you saved the file and restarts the server automatically before you've even lifted your hands off the keyboard. You write code, flip to the browser, and your change is already live.

I watched a junior dev spend forty-five minutes debugging what turned out to be a change he'd made that was never actually running — because he'd forgotten to restart his server after saving the file. Not once. Three times in the same afternoon. That's not a character flaw; it's a workflow problem, and Nodemon exists to eliminate it entirely.

Node.js doesn't watch your files. When you start a Node.js server with 'node app.js', it loads your code once into memory and runs it. The moment you change a file and save it, Node.js has absolutely no idea. It's still running the old code. The only way to pick up your changes is to kill the process and restart it manually. In a fast feedback loop where you're editing, checking, editing, checking — that's a tax you pay hundreds of times a day. It sounds minor until you've done it for three hours straight and your muscle memory is shot and you've introduced a bug because you were staring at stale output.

After reading this, you'll be able to install Nodemon, wire it into any Node.js project, configure it to watch exactly the files you care about, ignore the ones you don't, and set it up so that every developer who clones your repo gets the same workflow automatically. You'll also know the three configuration mistakes that cause Nodemon to restart infinitely or miss changes entirely — both of which I've seen derail entire dev sessions.

Why Nodemon Needs a Docker Fix on Mac

Nodemon is a file-watching utility that restarts a Node.js process when source files change. Its core mechanic: it monitors the filesystem for modification events and kills/restarts the running process automatically. On Mac inside Docker, the default polling mechanism fails because macOS's native file events (fsevents) don't propagate reliably through Docker's filesystem layers. The result: nodemon misses changes, and developers manually restart containers — defeating the purpose. The fix is legacyWatch: true, which forces nodemon to use filesystem polling instead of event-driven watching. Polling checks file timestamps every interval (default 1 second), trading CPU overhead for correctness. In practice, this means nodemon works reliably in Docker-on-Mac environments, but you must configure it explicitly — the default behavior is broken. Teams that skip this setting waste hours debugging 'why didn't my code reload?'

Default Watch Mode Is Broken on Mac + Docker
Nodemon's default watch mode uses fsevents, which Docker for Mac does not propagate. Without legacyWatch, changes are silently ignored.
Production Insight
A team deploys a microservice on Mac Docker dev environment; code changes don't trigger restarts, so devs manually restart containers every 2 minutes.
Symptom: nodemon process stays alive, logs show no restart after file save, but container is healthy.
Rule: If your dev environment uses Docker on Mac, always set nodemon's legacyWatch to true — never rely on default file events.
Key Takeaway
Nodemon's default watch mode uses OS-native events that fail on Docker for Mac.
Set legacyWatch: true to force polling — it's slower but reliable.
Without this fix, developers waste time manually restarting containers, breaking the feedback loop.
Nodemon Docker Mac LegacyWatch Fix Flow THECODEFORGE.IO Nodemon Docker Mac LegacyWatch Fix Flow Steps to fix stale code detection in Docker on Mac LegacyWatch Flag Enable polling for file changes in Docker Watch Specific Directories Limit nodemon to src/ and config/ Ignore node_modules and .git Prevent unnecessary file scanning Set Chokidar Polling Interval Use --polling-interval 1000ms Auto-Restart on Changes Nodemon detects saves and restarts ⚠ Polling can spike CPU on large projects Always ignore node_modules and set polling interval THECODEFORGE.IO
thecodeforge.io
Nodemon Docker Mac LegacyWatch Fix Flow
Nodemon Guide

Why Manual Restarts Kill Your Flow (And What Node.js Actually Does)

Before you can appreciate what Nodemon does, you need to understand why Node.js doesn't auto-restart by itself — and it's not an oversight, it's a deliberate design decision.

When you run 'node server.js', Node reads that file, compiles it to bytecode, loads it into memory, and starts executing. From that point on, Node is a running process. It owns a port, it holds database connections, it has state in memory. It doesn't scan your file system for changes because that's not its job. Its job is to serve requests as fast as possible. Polling the disk constantly would waste CPU cycles and slow everything down. So Node.js makes a deal with you: 'I'll be blazing fast, but you handle restarts.'

In development, that deal is terrible. You're changing files every two minutes. Every change requires a Ctrl+C and 'node server.js' again. Worse, you'll forget. You'll save a file, test your endpoint, get the old behaviour, spend ten minutes convinced your logic is wrong, and then finally notice you never restarted. I've seen this exact scenario cause someone to revert a perfectly correct fix because they thought it 'didn't work.'

Nodemon solves this by wrapping the Node.js process. It watches your project files for changes, and when it detects one, it kills the current Node.js process and starts a new one automatically. Your server is always running the latest version of your code. You save, you flip to Postman or your browser, your change is there.

BasicExpressServer.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// io.thecodeforge — JavaScript tutorial

// A minimal Express server — the kind you'd have at the very start
// of building a REST API for, say, a product catalogue service.
// Without Nodemon, every change to this file means manually restarting.

const express = require('express');

const app = express();
const PORT = 3000;

// Parse incoming JSON request bodies (needed for POST/PUT endpoints)
app.use(express.json());

// A simple health-check route — the first thing any real API should have.
// Load balancers and monitoring tools ping this to know the service is alive.
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'ok',
    timestamp: new Date().toISOString()
  });
});

// A placeholder products route — imagine this hitting a database next.
app.get('/products', (req, res) => {
  res.status(200).json({
    products: [
      { id: 1, name: 'Wireless Keyboard', price: 79.99 },
      { id: 2, name: 'USB-C Hub',        price: 49.99 }
    ]
  });
});

// Start the server and bind it to the port.
// Without Nodemon: change ANYTHING above, and this output is lying to you
// until you manually kill and restart the process.
app.listen(PORT, () => {
  console.log(`Product API running on http://localhost:${PORT}`);
  console.log('Tip: You are running the code that was loaded at START TIME.');
  console.log('Changes you make now will NOT appear until you restart.');
});
Output
Product API running on http://localhost:3000
Tip: You are running the code that was loaded at START TIME.
Changes you make now will NOT appear until you restart.
The Invisible Bug Trap:
The most common beginner mistake isn't a syntax error — it's testing against a running server that's still executing code from three saves ago. If you ever think 'this should be working but it's not,' the very first question is: did you restart? Nodemon makes this question irrelevant.
Production Insight
In production, you never want auto-restarts on file change. Node.js code is deployed once, runs until the next deployment. Nodemon in production would restart on log rotation or config file changes — dropping active connections.
Rule: 'start' script uses plain 'node', 'dev' script uses 'nodemon'. Never mix them.
Key Takeaway
Node.js doesn't watch files by design. Nodemon wraps the process to restart on changes.
Manual restarts are error-prone and kill flow.
Always separate dev and production start scripts.

Installing Nodemon and Running Your First Auto-Restarting Server

Here's exactly how to get Nodemon running from nothing. Two decisions upfront: global install vs. local install. I'll tell you the right answer and explain why the other one causes problems on a team.

Global install means you run 'npm install -g nodemon' and the 'nodemon' command becomes available everywhere on your machine. Sounds convenient. The problem is that your teammates might have a different version, or a CI/CD pipeline won't have it at all. Six months later someone clones your repo, runs the start script, gets 'nodemon: command not found', and wastes half an hour figuring out why. Don't do global installs for project tooling.

Local install — 'npm install --save-dev nodemon' — puts Nodemon in your project's node_modules folder and records it in package.json under devDependencies. Anyone who clones the repo and runs 'npm install' gets the exact same version of Nodemon automatically. This is the only approach that works on a team or in CI. The '--save-dev' flag is critical: Nodemon is a development tool, not something that should ever run in production. Separating dev from production dependencies keeps your production Docker image lean and your deployment safe.

Once installed locally, you can't just type 'nodemon server.js' in the terminal — your shell doesn't know where to find it. You access it through npm scripts in package.json, which automatically add your local node_modules/.bin to the PATH when running scripts.

PackageJsonSetup.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// io.thecodeforge — JavaScript tutorial

// This is your package.json — the configuration file at the root
// of every Node.js project. Think of it as your project's ID card
// and instruction manual combined.

// After running: npm install --save-dev nodemon
// Your package.json should look something like this:

{
  "name": "product-catalogue-api",
  "version": "1.0.0",
  "description": "REST API for product catalogue management",

  "scripts": {
    // 'start' is the production command — plain node, no Nodemon.
    // Never run Nodemon in production. It restarts on file changes,
    // which is the last thing you want on a live server.
    "start": "node server.js",

    // 'dev' is what every developer runs locally.
    // npm run dev => npm finds nodemon in node_modules/.bin automatically.
    // The --rs flag lets you manually type 'rs' + Enter in the terminal
    // to force a restart without changing any file (handy for env var changes).
    "dev": "nodemon server.js"
  },

  "dependencies": {
    // express belongs here — it runs in production
    "express": "^4.18.2"
  },

  "devDependencies": {
    // nodemon belongs HERE, not in dependencies.
    // --save-dev puts it here automatically.
    // This means 'npm install --production' skips it entirely.
    "nodemon": "^3.0.1"
  }
}
Output
// After running: npm run dev
// You will see output similar to this in your terminal:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Now edit ANY .js or .json file and save. You will immediately see:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
Senior Shortcut:
Always have a separate 'start' and 'dev' script in package.json. 'start' runs plain node for production, 'dev' runs nodemon for local development. If someone accidentally runs 'npm run dev' on a production server, Nodemon watching for file changes on a live machine is a security and stability nightmare — plus, any deployment tool that touches the filesystem would trigger constant restarts.
Production Insight
Global installs break team consistency and CI. Always install locally with --save-dev.
If someone gets 'nodemon: command not found' after cloning, they're missing the local install.
Add a 'dev' script in package.json so npm run dev works out of the box.
Key Takeaway
npm install --save-dev nodemon. Never --global.
Add 'dev' script to package.json.
Separate dev and production dependencies.

Configuring Nodemon: Watch the Right Files, Ignore the Rest

Out of the box, Nodemon watches all .js, .mjs, .cjs, and .json files in your project directory and restarts on any change. For a tiny project that's fine. For anything real, it'll drive you insane.

Here's the real problem: Nodemon doesn't know the difference between your source code and generated files, log files, or temporary files. If your app writes to a log file on every request — which is completely normal — and that log file is inside your project directory, Nodemon sees the write, thinks your code changed, and restarts. Which triggers another request. Which writes to the log. Which triggers another restart. I've seen this infinite restart loop on three separate teams. It eats CPU, it makes your logs unreadable, and it looks like the world's most confusing bug because the server is constantly restarting for no visible reason.

The fix is a nodemon.json configuration file. It sits at the root of your project alongside package.json and gives you precise control over what Nodemon watches, what it ignores, and what file extensions trigger a restart. Every serious Node.js project should have one. It makes the development experience consistent for every person on the team — nobody's getting phantom restarts because of local tooling differences.

nodemon.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// io.thecodeforge — JavaScript tutorial

// nodemon.json — place this at your project root.
// Nodemon reads this automatically when it starts. No flags needed.

{
  // 'watch' tells Nodemon exactly which directories to monitor.
  // Only changes inside these folders will trigger a restart.
  // Here we're only watching our source code folder, not the whole project.
  "watch": ["src", "config"],

  // 'ext' lists the file extensions that should trigger a restart.
  // We add 'env' because environment config changes need a restart.
  // We DON'T add 'log', 'tmp', or 'json' from generated folders.
  "ext": "js,json,env",

  // 'ignore' is your safety net — anything Nodemon should never restart for.
  // This is where you stop the infinite restart loop before it starts.
  "ignore": [
    "src/**/*.test.js",   // Don't restart when test files change — tests run separately
    "src/**/*.spec.js",   // Same for spec files
    "logs/*",             // Log files change on every request — NEVER watch these
    "node_modules/*",     // npm install changes these — never restart for this
    "coverage/*",         // Test coverage reports are generated, not source code
    "dist/*",             // Built/compiled output — not source code
    ".git/*"              // Git internals — definitely not source code
  ],

  // 'delay' adds a debounce in milliseconds before Nodemon restarts.
  // Without this, saving multiple files quickly (e.g. a refactor touching 5 files)
  // triggers 5 rapid restarts in a row instead of one clean restart.
  // 500ms is the sweet spot — long enough to batch saves, short enough to feel instant.
  "delay": 500,

  // 'env' injects environment variables specifically for the dev session.
  // This means you don't need a separate .env loader for local development basics.
  // For production, these come from your actual environment — NOT from here.
  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },

  // 'verbose' set to true prints exactly which file changed and why Nodemon restarted.
  // Turn this on when debugging a phantom restart — it'll name the culprit file immediately.
  "verbose": false
}
Output
// With this nodemon.json in place, running 'npm run dev' produces:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] watching extensions: js,json,env
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Edit src/routes/products.js and save — you see:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Write to logs/app.log — Nodemon stays silent. No restart. Perfect.
The Infinite Restart Loop:
If your app writes to any file inside a watched directory — logs, generated files, cache files — Nodemon will restart every time that file is written, which causes the app to start again, write to the file again, and restart again forever. Symptom: '[nodemon] restarting due to changes...' appearing continuously with no file saves on your end. Fix: add the offending directory to the 'ignore' array in nodemon.json. Run with 'verbose: true' first to see exactly which file is causing it.
Production Insight
The infinite restart loop is the #1 production incident in dev environments. It's not a bug — it's a config gap.
Always add 'logs/', 'dist/', 'node_modules/', '.git/' to ignore before you hit this.
Use 'delay': 500 to batch rapid saves and reduce unnecessary restarts.
Key Takeaway
Use nodemon.json for all config.
Ignore directories that the app writes to.
Set delay to 500ms to debounce multi-file saves.

Nodemon With ES Modules, TypeScript, and Multi-Process Apps

Basic Nodemon for a CommonJS Express app is straightforward. But modern Node.js projects use ES Modules ('import'/'export' instead of 'require'), TypeScript, or even multiple processes. Each one needs a small adjustment or you'll hit a wall and not know why.

ES Modules in Node.js require either a .mjs extension or '"type": "module"' in your package.json. Nodemon itself handles this fine — the restart mechanism doesn't care about your module system. But the command you give Nodemon matters. If you're using '"type": "module"' and running Node 18+, plain 'nodemon server.js' still works. No change needed there.

TypeScript is the one that trips people up. Nodemon can't execute TypeScript directly — Node.js can't either. You need a TypeScript executor. The most common choice for development is 'ts-node'. You tell Nodemon to use ts-node as the executor instead of node, and it handles compilation on the fly. For larger projects, 'tsx' has become the faster alternative — it's built on esbuild and noticeably quicker for big codebases. I've seen teams on large TypeScript monorepos where ts-node's restart time was 8-12 seconds per change. Switching to tsx dropped that to under 2 seconds. That compounds over a full work day into real productivity.

For any of these setups, the exec key in nodemon.json is your control lever. It replaces 'node' with whatever executor you need.

nodemon.typescript.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// io.thecodeforge — JavaScript tutorial

// nodemon.json configured for a TypeScript Node.js project.
// This is the setup you'd use for a TypeScript Express API —
// the kind of thing you'd find in a fintech or SaaS backend.

{
  // 'watch' stays the same — source code directories only
  "watch": ["src", "config"],

  // Add 'ts' to the extension list — Nodemon now triggers on .ts file changes too
  "ext": "ts,js,json",

  "ignore": [
    "src/**/*.test.ts",
    "src/**/*.spec.ts",
    "dist/*",           // TypeScript compiles TO dist/ — never watch compiled output
    "node_modules/*",
    "logs/*"
  ],

  // 'exec' is the key that replaces 'node' with a different runtime/executor.
  // Here we use 'tsx' — dramatically faster than ts-node for large codebases.
  // Install it with: npm install --save-dev tsx
  // For ts-node instead: "exec": "ts-node src/server.ts"
  "exec": "tsx src/server.ts",

  "delay": 500,

  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  }
}

// ─────────────────────────────────────────────────────────
// Your package.json scripts section with TypeScript:
// ─────────────────────────────────────────────────────────
// {
//   "scripts": {
//     "start": "node dist/server.js",        // Production: runs compiled JS
//     "build": "tsc",                         // Compile TS to JS for production
//     "dev":   "nodemon"                      // Dev: nodemon reads nodemon.json automatically
//   }
// }
//
// Note: 'nodemon' with no arguments reads nodemon.json and uses the 'exec' command.
// This is cleaner than putting everything in the npm script as flags.
Output
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] watching extensions: ts,js,json
[nodemon] starting `tsx src/server.ts`
Product API (TypeScript) running on http://localhost:3000
// Edit src/routes/products.ts and save:
[nodemon] restarting due to changes...
[nodemon] starting `tsx src/server.ts`
Product API (TypeScript) running on http://localhost:3000
Interview Gold:
Nodemon is a development-only tool. In production, Node.js process management belongs to PM2 (which handles clustering, zero-downtime restarts, and process resurrection on crash) or to your container orchestrator like Kubernetes. If an interviewer asks 'how do you handle Node.js process management in production?' and you say 'Nodemon,' you've just told them you've never shipped a real production service.
Production Insight
For TypeScript projects, tsx reduces restart time from ~10s (ts-node) to ~2s on large codebases. That's a measurable productivity gain.
Always set 'exec' in nodemon.json — don't rely on shell aliases or npm scripts flags.
Remember: production runs compiled JS, not TypeScript. Keep build and dev scripts separate.
Key Takeaway
Use exec key for TypeScript/ESM.
Prefer tsx over ts-node for speed.
Keep production start script with plain node.

Nodemon in Docker and Team Workflows

Running Nodemon inside a Docker container is common for development, but it introduces a critical gotcha: file change detection across volume mounts.

On Linux hosts running Docker natively, filesystem events (inotify) propagate correctly. Nodemon works out of the box. But on macOS (Docker Desktop) and Windows, the host filesystem is mounted via a network-like layer (osxfs on Mac, SMB on Windows). These layers do not forward inotify/FSEvents signals into the container. Nodemon sees nothing. You edit a file on your host, the container's filesystem updates, but Nodemon's watcher receives no event. You stare at a stale server.

The fix is polling mode. Set '"legacyWatch": true' in nodemon.json. This tells Nodemon's chokidar to poll the filesystem every few seconds instead of waiting for events. It uses more CPU (about 2-5% on a modern machine) but it's reliable across all Docker environments. Set the polling interval via '"pollingInterval": 1000' (milliseconds) to control frequency.

Another team workflow concern is consistent Nodemon configuration across multiple developers. The only way to guarantee this is to commit a nodemon.json file to your repository. Do not rely on each developer having the right global config or CLI flags. The nodemon.json file is checked in, versioned, and every team member sees exactly the same behaviour. If someone needs a local override (e.g., different port), they can use environment variables instead of modifying the committed config.

nodemon.docker.jsonJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// io.thecodeforge — JavaScript tutorial

// nodemon.json for Docker development on macOS/Windows
// Enables polling mode to work around missing filesystem events

{
  "watch": ["src", "config"],
  "ext": "js,json,env",
  "ignore": [
    "src/**/*.test.js",
    "src/**/*.spec.js",
    "logs/*",
    "node_modules/*",
    "coverage/*",
    "dist/*",
    ".git/*"
  ],
  "delay": 500,

  // Enable polling mode for Docker volume mount compatibility
  "legacyWatch": true,

  // Poll every 1000ms (default is 1000 if not set)
  "pollingInterval": 1000,

  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },

  "verbose": false
}

// ─────────────────────────────────────────────────────────
// Dockerfile.development snippet (optional):
// ─────────────────────────────────────────────────────────
// FROM node:20-alpine
// WORKDIR /app
// COPY package*.json ./
// RUN npm install
// COPY . .
// # Run with polling mode already set in nodemon.json
// CMD ["npm", "run", "dev"]
Output
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/* config/**/*
[nodemon] legacy watch mode (polling) enabled
[nodemon] watching extensions: js,json,env
[nodemon] starting `node server.js`
Product API running on http://localhost:3000
// Edit a file on host — change is detected within ~1 second (polling interval)
Docker Pro Tip:
If Nodemon works perfectly on your Linux machine but fails on a colleague's Mac, the culprit is always legacyWatch mode. Add it to the project’s nodemon.json preemptively — it adds a tiny CPU cost but eliminates an entire class of 'works on my machine' problems.
Production Insight
Polling mode uses about 3% CPU on a 4-core machine — negligible for development, but avoid in production (which you wouldn't use Nodemon for anyway).
If you have a team of 10, and half use macOS, committing a polling-enabled nodemon.json saves an estimated 2 hours per new developer onboarding.
Never assume filesystem events work across Docker volume mounts. Document the platform-specific config in README.
Key Takeaway
For Docker on macOS/Windows, set 'legacyWatch': true.
Polling uses ~3% CPU but works everywhere.
Always commit nodemon.json to repo for team consistency.

The One Config Mistake That Kills CPU on Large Projects

Nodemon's default behavior is to watch every file in your project directory. On a small Express API, that's fine. On a monorepo with 10,000+ files in node_modules, generated build artifacts, and log files — it'll peg your CPU at 100% and your editor will lag.

The root cause isn't nodemon being greedy. It's that developers don't understand how file-watching works under the hood. Nodemon relies on fs.watch or chokidar, which recursively scans directories for changes. Every time a file is saved, nodemon registers a change event, restarts your app, and re-scans the entire watch tree. Multiply that by hundreds of files, and you've got a feedback loop that kills performance.

The fix is brutally simple: be explicit about what nodemon should watch. Use the --watch flag or the nodemon.json config file to narrow the scope to your source directory only. Never watch node_modules, dist, .git, or log directories. The rule: if you didn't write it, don't watch it. Your CPU will thank you.

StrictWatchConfig.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — javascript tutorial

// nodemon.json — production-grade watch configuration
{
  "watch": ["src/"],
  "ext": "js,mjs,cjs,json,ts",
  "ignore": ["node_modules", "dist", "coverage", "*.log", ".git"],
  "delay": "500ms",
  "legacyWatch": false,
  "quiet": false
}

// Terminal output when nodemon runs with this config:
// [nodemon] watching path(s): src/**/*
// [nodemon] watching extensions: js,mjs,cjs,json,ts
// [nodemon] starting `node src/index.js`
// [nodemon] clean exit - waiting for changes before restart
Output
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: js,mjs,cjs,json,ts
[nodemon] starting `node src/index.js`
Production Trap:
Never set 'ignore' patterns in nodemon.json that overlap with your project's gitignore. Nodemon can't watch directories it can't traverse.
Key Takeaway
Always configure nodemon's watch and ignore lists per project — never rely on defaults for any codebase larger than a few hundred files.

Debugging Nodemon With Breakpoints — The Clean Way

Developers love nodemon for the auto-restart. They hate it when they attach a debugger and restarting the app kills their breakpoints and drops the debug session. The standard approach — using node --inspect with nodemon — is fragile because nodemon restarts the process, which releases the debug port.

Here's the production pattern: use nodemon's --inspect flag or pass the Node.js inspect argument through nodemon's exec map. This tells nodemon to manage the debugger lifecycle. When your app restarts, nodemon re-attaches the debugger to the new process on the same port. No session drops. No manual re-connect.

Second, set a delay for restarts during debugging. The --delay flag with a value like 2000ms gives you a two-second window after saving a file before nodemon kills the process. That extra time lets your debugger catch the last state of the old process. Without it, you can lose the context of the bug you were chasing.

Finally, combine this with source maps for TypeScript or Babel projects. If nodemon isn't configured to use transpiled source maps, your debugger will show you compiled code — not the original TypeScript you wrote. That's a waste of time. Add "execMap": {"ts": "node --require ts-node/register --inspect=9229"} to your config.

DebuggerReady.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — javascript tutorial

// nodemon.json — debugger-friendly config
{
  "watch": ["src/"],
  "ext": "ts,js,json",
  "execMap": {
    "ts": "node --require ts-node/register --inspect=9229"
  },
  "delay": "2000ms",
  "signal": "SIGTERM"
}

// Terminal output when starting with debug flag:
// $ nodemon --inspect src/index.ts
//
// [nodemon] starting `node --inspect=9229 src/index.ts`
// Debugger listening on ws://127.0.0.1:9229/...
// [nodemon] restarting due to changes...
// [nodemon] starting `node --inspect=9229 src/index.ts`
// Debugger listening on ws://127.0.0.1:9229/...
Output
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts,js,json
[nodemon] starting `node --inspect=9229 src/index.ts`
Debugger listening on ws://127.0.0.1:9229/...
Senior Shortcut:
Use nodemon's --inspect flag instead of manually passing --inspect to Node. Nodemon preserves the debug port across restarts automatically.
Key Takeaway
Always configure nodemon with the --inspect flag and a --delay of at least 1s when debugging — this prevents debug session loss and gives you time to inspect state before restart.

Introduction

Developing Node.js applications often involves a tedious cycle: make a code change, stop the server, and restart it manually. Nodemon eliminates this friction by automatically monitoring your project files and restarting the Node process whenever a change is detected. It wraps your application in a file-watching layer that triggers graceful restarts—preserving your debugging state when combined with the right flags. This guide covers everything from local setup to team workflows, with a focus on why each configuration choice matters. By the end, you'll avoid the common pitfalls that waste CPU cycles and break Docker containers. Nodemon is not just a convenience tool; it's a reliability layer that keeps your feedback loop tight during development, letting you stay in flow state without breaking concentration on manual restarts.

Key Insight:
Nodemon does not execute your JavaScript—it wraps your runtime and detects file changes via fs.watch or chokidar.
Key Takeaway
Nodemon automates Node.js server restarts on file changes, improving developer productivity.

Prerequisites

Before installing Nodemon, ensure you have Node.js version 14 or higher installed on your system. You can verify this by running node --version in your terminal. A basic understanding of the command line and package.json scripts is helpful. If you are using Docker or working with TypeScript, ES modules, or multi-process apps, you will need additional configuration—covered later. No prior Nodemon experience is required; this guide starts from scratch. For local development, you should also have a Node.js project initialized (npm init) and a working server file (e.g., server.js or index.js). If you already have a project, make sure your dependencies are installed. These prerequisites ensure that Nodemon can correctly hook into your application's lifecycle without interference from missing modules or incompatible runtime versions.

check-version.jsJAVASCRIPT
1
2
3
4
5
6
7
// io.thecodeforge — javascript tutorial
console.log('Node version:', process.version);
if (Number(process.version.slice(1).split('.')[0]) < 14) {
  console.error('Upgrade Node.js to v14+ for Nodemon compatibility');
} else {
  console.log('✅ Node version meets requirements');
}
Production Trap:
Nodemon should never be used in production—it increases CPU usage and can leak memory from long-running file watches.
Key Takeaway
Node.js v14+ and a working project are required before installing Nodemon.
● Production incidentPOST-MORTEMseverity: high

Silent Stale Code on Docker for Mac

Symptom
Developers on macOS using Docker Compose with a Node.js container run 'npm run dev' inside the container. They edit source files on the host, save, but Nodemon never triggers a restart. The server continues running old code.
Assumption
Everyone assumed Nodemon's default file-watching mechanism works identically across all platforms. It doesn't. Docker for Mac uses osxfs, which does not forward inotify events from the host into the container.
Root cause
Nodemon's default file watcher relies on inotify (Linux kernel) or FSEvents (macOS) internally. When running inside a Docker container on macOS, the host macOS sends file changes through a network filesystem layer that doesn't relay native filesystem events. Nodemon's chokidar library never receives the change signal.
Fix
Enable 'legacyWatch' mode in nodemon.json with a polling interval. This forces Nodemon to poll the filesystem on a timer instead of relying on event-driven notifications.
Key lesson
  • If Nodemon works on native Linux but not in Docker on macOS, the fix is almost always legacyWatch: true in nodemon.json.
  • Always test your development Docker setup with a known file change before a full sprint. Catch this on day one, not day five.
  • Document platform-specific configurations in your project README so every new developer doesn't have to rediscover this.
Production debug guideCommon scenarios where Nodemon appears to ignore file changes or restarts too often4 entries
Symptom · 01
Nodemon shows no restart after saving a file
Fix
Check if legacyWatch mode is required (Docker for Mac/Windows). Run with --legacy-watch flag. If that fixes it, add '"legacyWatch": true' to nodemon.json.
Symptom · 02
Nodemon restarts continuously without any file saves
Fix
Set '"verbose": true' in nodemon.json. Look for the file path printed after 'restarting due to changes...'. Add that path or its parent directory to the 'ignore' array.
Symptom · 03
Nodemon restarts only on .js changes, not .ts or .env
Fix
Check the 'ext' key in nodemon.json. It must include 'ts', 'json', 'env' etc. Default is 'js,mjs,cjs,json'.
Symptom · 04
Nodemon starts but never runs the correct entry file
Fix
Verify the 'exec' key in nodemon.json or the command passed to nodemon. For TypeScript projects, ensure tsx or ts-node is installed and referenced.
★ Nodemon Debug Cheat SheetFive common Nodemon problems and exactly what to run to fix them
No restart on file save
Immediate action
Run nodemon with polling: nodemon --legacy-watch server.js
Commands
nodemon --legacy-watch --verbose server.js
Add '"legacyWatch": true' to nodemon.json
Fix now
echo '{"legacyWatch": true}' > nodemon.json && nodemon
Infinite restart loop+
Immediate action
Stop nodemon (Ctrl+C). Check which directory is being written to.
Commands
nodemon --verbose server.js 2>&1 | grep -i 'restarting due to changes'
Add the offending directory to nodemon.json ignore array
Fix now
Add to ignore: '"ignore": ["logs/*"]' then restart
TypeScript file changes not triggering restart+
Immediate action
Verify ext includes 'ts' in nodemon.json
Commands
nodemon --ext ts,js,json --exec tsx src/server.ts
Move configuration to nodemon.json with exec and ext keys
Fix now
Create nodemon.json with '"ext": "ts,js,json", "exec": "tsx src/server.ts"'
Nodemon not found after npm install+
Immediate action
Check package.json devDependencies have nodemon
Commands
npm ls nodemon
npx nodemon server.js (runs from local node_modules)
Fix now
npm install --save-dev nodemon && npx nodemon server.js
Rapid restarts when saving multiple files+
Immediate action
Add a delay of 500ms to batch changes
Commands
nodemon --delay 500ms server.js
Add '"delay": 500' to nodemon.json
Fix now
echo '{"delay": 500}' > nodemon.json and restart
Feature / AspectNodemonNode.js --watch (built-in, Node 18+)
Installation requiredYes — npm install --save-dev nodemonNo — built into Node.js 18+
Configuration file supportFull nodemon.json with ignore, delay, exec, envLimited — flags only, no dedicated config file
Custom executor (e.g. tsx, ts-node)Yes — via 'exec' key in nodemon.jsonNo — only runs node itself
File extension filteringYes — 'ext' key, granular controlBasic — --watch-path flag only
Ignore patternsGlob patterns in 'ignore' arrayNot supported as of Node 20
Restart debounce / delayYes — 'delay' key in millisecondsNo — restarts immediately on every change
Manual restart command ('rs')Yes — type 'rs' in terminal to force restartNo
TypeScript / ESM project supportYes — via custom 'exec' commandOnly plain .js and .mjs files
Maturity / community10+ years, battle-tested, 25M+ weekly downloadsExperimental in Node 18, stable in Node 22 but limited
Best forAny real project, especially TypeScript or complex setupsQuick one-off scripts, zero-dependency environments

Key takeaways

1
Nodemon belongs in devDependencies only. The moment it appears in a production dependency list or a production process manager, something has gone wrong with your deployment pipeline.
2
The infinite restart loop
where Nodemon restarts because the running app writes to a watched directory — is the most common Nodemon-related incident on development teams. Add your logs/, dist/, and coverage/ directories to the 'ignore' array in nodemon.json before you ever hit this.
3
Reach for nodemon.json the moment your project has more than one developer or more than two configuration flags. Inline flags in npm scripts don't scale, can't be documented inline, and create config drift between team members.
4
Nodemon is a development convenience tool, not a production process manager. If you're thinking about restart behaviour, crash recovery, or zero-downtime deploys in production
you want PM2, systemd, or your container orchestrator. Nodemon has never been designed for that job and won't do it reliably.
5
On macOS or Windows with Docker, enable legacyWatch polling mode in your nodemon.json proactively. It adds ~3% CPU overhead but saves hours of debugging silent missed restarts.

Common mistakes to avoid

5 patterns
×

Installing Nodemon as a regular dependency (npm install nodemon without --save-dev)

Symptom
Nodemon ends up in production Docker images, inflating container size and introducing file-watching overhead on live servers where it should never run. It also gets deployed to production and may restart on file writes, dropping connections.
Fix
Move it to devDependencies manually in package.json, or reinstall with 'npm install --save-dev nodemon' and remove from dependencies. Verify with 'npm ls --prod' to confirm Nodemon is not in production.
×

Watching a directory that the running app writes to (e.g., logs folder inside src/)

Symptom
Nodemon restarts infinitely: '[nodemon] restarting due to changes...' appears every few seconds with no user-triggered saves. CPU spikes and logs become unreadable.
Fix
Add the offending directory to the 'ignore' array in nodemon.json (e.g., '"ignore": ["logs/*"]'). Run Nodemon with '"verbose": true' first to identify exactly which file is causing the phantom restarts.
×

Putting Nodemon configuration as CLI flags in package.json scripts instead of a nodemon.json file

Symptom
The script becomes unreadable: 'nodemon --watch src --ext ts,js --delay 500 server.js'. Teammates add conflicting flags, and the configuration isn't version-controlled cleanly. New developers don't know what flags are expected.
Fix
Move all flags to a nodemon.json file at the project root and run just 'nodemon' in the npm script. Nodemon reads the config file automatically, ensuring consistency across all developers.
×

Using Nodemon in production (accidentally via a 'start' script that calls nodemon, or by copying the 'dev' script)

Symptom
Any file change on the production server (log rotation, deployment file writes, config updates) triggers a restart mid-request, causing dropped connections and 503 errors. CPU usage also spikes due to polling.
Fix
Ensure 'start' in package.json always uses plain 'node', and configure your production process manager (PM2, systemd, or Kubernetes) to use the start script, never the dev script. Add a CI check that fails if 'nodemon' appears in 'dependencies' or 'scripts' with 'start'.
×

Not setting a debounce delay for multi-file saves

Symptom
When a developer saves multiple files at once (e.g., during a refactor), Nodemon restarts multiple times in rapid succession, causing the server to be unavailable for several seconds. Each restart kills and re-establishes database connections, wasting time.
Fix
Add '"delay": 500' (or higher) to nodemon.json. This debounces changes: if multiple files are saved within 500ms, only one restart occurs.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Nodemon watches for file changes and restarts your Node.js process — but...
Q02SENIOR
When would you choose Nodemon over Node.js's built-in --watch flag intro...
Q03SENIOR
If a team reports that their Node.js dev server is restarting every 2-3 ...
Q01 of 03SENIOR

Nodemon watches for file changes and restarts your Node.js process — but what's the underlying mechanism it uses to detect changes, and what are the implications of that mechanism on network file systems like NFS or Docker volume mounts on Windows?

ANSWER
Nodemon uses the chokidar library under the hood, which relies on native filesystem events: inotify on Linux, FSEvents on macOS, and ReadDirectoryChangesW on Windows. On network file systems like NFS or Docker volume mounts on macOS/Windows, these native events may not propagate reliably. The fallback is polling mode (legacyWatch), where chokidar periodically checks file modification times instead of waiting for events. Polling is CPU-intensive but cross-platform reliable. The key insight is that developers on macOS using Docker must enable 'legacyWatch' in nodemon.json, or file changes will silently be ignored.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Does nodemon work with TypeScript projects?
02
What's the difference between nodemon and node --watch?
03
How do I stop nodemon from restarting when I don't want it to?
04
Is it safe to run nodemon in a Docker container during development?
05
Should I commit nodemon.json to version control?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
May 24, 2026
last updated
1,663
articles · all by Naren
🔥

That's Node.js. Mark it forged?

10 min read · try the examples if you haven't

Previous
NVM — Node Version Manager: Install and Switch Node Versions
17 / 18 · Node.js
Next
MERN Stack: MongoDB, Express, React, and Node.js