If your dev server dies with Error: ENOSPC: System limit for number of file watchers reached, it has nothing to do with disk space. You've hit the Linux kernel's inotify watch limit. You have three fixes: raise fs.inotify.max_user_watches on the host, make your tools watch fewer directories, or switch to polling when inotify can't work at all (some Docker bind mounts, network drives).

The right choice depends on which of those situations you're in, and the order matters. Raising the limit is the fastest fix, but it hides the real problem if a tool is watching node_modules for no reason.

What does ENOSPC mean when there's plenty of disk space?

ENOSPC is "no space left on device", but the kernel reuses it for inotify. The message usually looks like this:

Error: ENOSPC: System limit for number of file watchers reached, watch '/home/dev/app/src/components' at FSWatcher.<computed> (node:internal/fs/watchers:247:19) 

Two different limits produce it:

Limit What it caps Typical symptom
fs.inotify.max_user_watches Total watches per user (roughly one per watched directory) ...System limit for number of file watchers reached, watch '<path>'
fs.inotify.max_user_instances Number of inotify handles per user Same ENOSPC, but no path, or inotify_init failing

Linux inotify watches directories, not individual files, so the cost scales with directory count. A monorepo with several thousand folders, plus a bundler, a test runner, a TypeScript server and an editor all watching the same tree, can use up the limit quickly. As of kernel 5.11 the default max_user_watches scales with system RAM (up to about a million); older kernels and many distro configs default to 8192, which is where most people hit it.

Takeaway: ENOSPC from a watch call is an inotify quota error, and the quota is per user, shared by every tool you have running.

How do I see who is using all the watches?

Before you change anything, check the current limits and find the biggest consumer:

cat /proc/sys/fs/inotify/max_user_watches cat /proc/sys/fs/inotify/max_user_instances # Watches per process (run with sudo to see other users' processes) for fd in $(find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null); do pid=$(echo "$fd" | cut -d/ -f3) n=$(grep -c '^inotify' "/proc/$pid/fdinfo/$(basename "$fd")" 2>/dev/null) echo "$n $pid $(tr '\0' ' ' < "/proc/$pid/cmdline" | cut -c1-80)" done | sort -rn | head 

In my experience the top entry is rarely the thing you expected. An editor's file watcher, a second dev server you forgot about, or a Jest watch mode running with the whole repo as its root are common culprits. If one process owns most of the watches and it's watching node_modules or .git, fix that first (next sections) rather than raising the limit.

To estimate what a single tool needs, count directories it would actually watch:

find . -type d -not -path '*/node_modules/*' -not -path '*/.git/*' | wc -l 

Takeaway: one process typically owns most of the watches, and finding it takes thirty seconds.

How do I raise the inotify limit permanently?

This is the standard fix on a Linux dev machine or CI runner. A one-off change:

sudo sysctl fs.inotify.max_user_watches=524288 sudo sysctl fs.inotify.max_user_instances=512 

To persist across reboots, put it in a drop-in file:

printf 'fs.inotify.max_user_watches=524288\nfs.inotify.max_user_instances=512\n' \ | sudo tee /etc/sysctl.d/99-inotify.conf sudo sysctl --system 

The numbers above are common community values, not magic. Each watch costs kernel memory (on the order of a kilobyte or two on 64-bit systems), and that memory is unswappable, so setting the limit to the maximum on a small VM is not free. Size it to your tree plus headroom.

Where you apply it matters. The limit lives in the kernel, so:

  • On a Linux host running containers, set it on the host. Containers share the host kernel.
  • On Docker Desktop for Mac or Windows, the "host" is the Linux VM Docker runs, not your laptop OS.
  • In WSL2, set it inside the WSL distribution.
  • In managed CI, you often can't change it, which pushes you toward the next two options.

Takeaway: raise max_user_watches on the machine whose kernel is actually doing the watching, and write it to /etc/sysctl.d/ so it survives a reboot.

How do I make my tools watch fewer directories?

Raising the limit treats the symptom. Excluding directories that never change at dev time treats the cause. Settings for the common tools (as of late 2026, check your version's docs):

Tool Setting
Vite server.watch.ignored
webpack / webpack-dev-server watchOptions.ignored
Next.js Uses webpack/Turbopack watchers; keep generated and output directories out of the project root where possible
Jest watchPathIgnorePatterns, or --no-watchman to avoid the Watchman crawler
VS Code files.watcherExclude
tsc --watch watchOptions.excludeDirectories in tsconfig.json

For example, a Vite config that stops watching build output and a vendored directory:

// vite.config.ts import { defineConfig } from 'vite'; export default defineConfig({ server: { watch: { ignored: ['**/dist/**', '**/.turbo/**', '**/vendor/**'], }, }, }); 

And for the TypeScript watcher:

{ "watchOptions": { "excludeDirectories": ["**/node_modules", "**/dist"] } } 

A caveat from experience: ignoring a directory means edits there will not trigger a rebuild. If you symlink a local package into node_modules for development, excluding node_modules will make that package's changes invisible, so check before you ignore.

If you run several tools that each crawl the repo, Watchman is the one that gives you a single shared watcher service instead of one crawler per tool, which Jest and Metro can use. Its drawback is that on Linux it still uses inotify underneath, so it reduces duplication but does not remove the need for a sane limit, and it adds a daemon to maintain.

Takeaway: if a tool is watching node_modules or build output, exclude them in its config before you touch the kernel setting.

When should I switch to polling instead?

Polling stat-checks files on a timer, so it uses no inotify watches at all. It trades CPU and latency for reliability. Use it when inotify genuinely can't see changes, not as a reflex:

  • Bind mounts from macOS or Windows into a Linux container often don't deliver filesystem events reliably, so hot reload silently never fires.
  • Network filesystems (NFS, SMB) generally don't produce inotify events for changes made on other machines.
  • Locked-down CI where you can't change sysctl values.

Common switches:

# chokidar-based tools (older webpack setups, nodemon alternatives) CHOKIDAR_USEPOLLING=true npm run dev # Next.js (webpack watcher) WATCHPACK_POLLING=true npm run dev 
// vite.config.ts export default defineConfig({ server: { watch: { usePolling: true, interval: 300 } }, }); 

Polling a large tree at a short interval will visibly burn CPU. Pair it with the ignore lists above so you only poll source directories, and keep the interval as long as you can tolerate.

Takeaway: polling is the fallback for places where inotify events never arrive, and it needs an ignore list to stay cheap.

FAQ

How do I fix "ENOSPC: System limit for number of file watchers reached"?
Run sudo sysctl fs.inotify.max_user_watches=524288 and persist it in /etc/sysctl.d/99-inotify.conf. If a tool is watching node_modules or build output, exclude those paths too.

Does ENOSPC in Node mean my disk is full?
Not when the message mentions "file watchers" or a watch call. That is the inotify watch or instance limit. A genuinely full disk shows up in write operations, and df -h will confirm it.

Do I need to set the inotify limit inside the Docker container?
No, the container shares the host kernel, so set it on the Linux host or in the Docker Desktop VM. Containers typically can't change it with their own sysctl settings.

Bottom line

On a Linux machine you control, raise fs.inotify.max_user_watches via /etc/sysctl.d/, but first spend a minute finding which process holds the watches and excluding node_modules and build output from it. If you're on a bind mount or network drive where events never arrive, use polling with a tight ignore list. If you can't change kernel settings, shrinking the watched set is your only real lever.

Related reading