Junior 14 min · March 28, 2026

NVM Node Version Manager — Stop OS Node Updates Breaking CI

Ubuntu 22.04 upgrade broke Node builds with 'crypto.createCipher is not a function' at 6am.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

Follow
Production
production tested
May 23, 2026
last updated
1,663
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • NVM installs Node.js versions in isolated directories under ~/.nvm, switching instantly without downloads.
  • Install via curl script, then source your shell config—'nvm: command not found' is always a PATH issue.
  • nvm install [version] downloads Node; nvm use [version] switches the current shell's environment.
  • Each Node version has its own global npm packages—they don't survive version switches without --reinstall-packages-from.
  • Use .nvmrc to pin project versions; combine with auto-switching shell hook for zero-friction team setups.
✦ Definition~90s read
What is NVM?

NVM (Node Version Manager) is a per-user shell-level tool that lets you install, switch between, and manage multiple isolated Node.js versions without sudo or system-level conflicts. It exists because OS package managers (apt, brew, yum) pin you to a single, often outdated Node version, and global npm install -g commands create a tangled mess across projects.

Think of Node.js versions like different models of the same power tool.

When your CI pipeline breaks because a developer’s laptop has Node 20 but your production Docker image runs Node 18, NVM is the fix: it enforces per-project version control via a .nvmrc file, so nvm use loads the exact version your code expects. Alternatives like fnm (faster, Rust-based) or volta (automatic, project-aware) exist, but NVM remains the most widely adopted due to its simplicity and shell integration.

Don’t use NVM if you need zero-config team onboarding—Volta handles that better—or if you’re on Windows (use nvm-windows or fnm instead). For Linux/macOS teams shipping to CI, NVM is the battle-tested standard: it’s what tools like CircleCI, GitHub Actions, and Dockerfiles reference in their Node setup steps, and it prevents the silent version drift that turns a npm ci success into a production crash.

Plain-English First

Think of Node.js versions like different models of the same power tool. Your old kitchen renovation project needs the 2018 drill with the specific bit size. Your new bathroom project needs the 2023 model with a completely different chuck. NVM is your tool cabinet — every version sits in its own drawer, labelled, and you just pull out whichever one the current job needs. No throwing away tools. No borrowing from one job to screw up another.

I've watched a developer nuke a client's staging environment at 11pm because they ran 'npm install -g' after upgrading Node globally and silently broke every other project on the machine. Not a config error. Not a bad package. Just the wrong Node version running the wrong code — and no way to roll back fast. That's the kind of pain NVM exists to prevent.

The problem is that Node.js moves fast. A project you started two years ago might need Node 14. Your new microservice was built on Node 20. Your company's legacy monolith flatly refuses to start on anything above Node 16 because of a deprecated crypto API. Installing Node the 'normal' way — downloading it from nodejs.org and running the installer — gives you exactly one version at a time, globally, for everything. The moment you need two versions for two projects, you're either Googling workarounds or breaking something.

By the end of this, you'll know how to install NVM from scratch on macOS or Linux, install multiple Node versions, switch between them per-project automatically, and never think about version conflicts again. You'll also understand the one file — .nvmrc — that makes your whole team use the same Node version without anyone having to say a word about it.

Why NVM Exists — Stop OS Node Updates Breaking CI

NVM (Node Version Manager) is a per-shell tool that lets you install, switch, and alias multiple Node.js versions without touching the system install. It works by manipulating PATH so the desired Node binary takes precedence — no symlinks, no global overrides. The core mechanic: nvm use 18 sets $PATH to point at ~/.nvm/versions/node/v18.x.x/bin, making that version active for the current terminal session only.

NVM stores each Node version in its own isolated directory under ~/.nvm. Switching versions is O(1) — just a PATH change. It supports .nvmrc files for per-project version pinning, and nvm install downloads prebuilt binaries from Node.js official dist. No root access needed. The key property: different terminals can run different Node versions simultaneously without conflict.

Use NVM in any environment where multiple projects require different Node majors — common in monorepos or teams with legacy services. In CI, pin the version via .nvmrc and run nvm install && nvm use before builds. Without it, OS package managers (apt, brew) silently upgrade Node, breaking CI pipelines that depend on specific APIs or module behavior.

NVM Is Not a System Package Manager
NVM only controls the active Node version in your shell — it does not replace apt/brew for global tools like npm or yarn, which must be reinstalled per Node version.
Production Insight
Team had a monorepo with services on Node 14 and 18. OS auto-updated Node to 20, breaking the Node 14 service's use of fs.promises API that changed behavior. CI failed with cryptic ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. Rule: always pin Node version per project with .nvmrc and enforce it in CI — never rely on the system Node.
Key Takeaway
NVM isolates Node versions per shell, not per machine — use it to avoid global version conflicts.
Always commit a .nvmrc file to every project repo — it's the single source of truth for the required Node version.
In CI, run nvm install before any Node command — never assume the CI runner's default Node matches your project.
NVM Node Version Manager Workflow THECODEFORGE.IO NVM Node Version Manager Workflow From OS node issues to team-wide version control OS Node Install Global install breaks CI on updates Install NVM Per-user version manager Install Node Versions nvm install Use .nvmrc File Team-wide version pinning Manage Global Packages Per-version isolation ⚠ Global Node installs will eventually burn you Always use NVM to avoid OS update conflicts THECODEFORGE.IO
thecodeforge.io
NVM Node Version Manager Workflow
Nvm Node Version Manager

Why Global Node Installs Will Eventually Burn You

Before NVM existed, the only way to change your Node version was to uninstall it and reinstall a different one. Full stop. People wrote shell scripts to automate the pain. Others just pinned everything to whatever Node version happened to be on the machine that week and hoped for the best.

The catastrophic version of this plays out when you join a team. The repo has no documented Node version. You run 'npm install', get 47 peer dependency warnings, and your app crashes on startup with 'TypeError: crypto.createCipher is not a function' — because createCipher was removed in Node 16 and you're running 18. You spend three hours debugging what looks like a broken crypto library before someone on Slack says 'oh yeah, use Node 14 for that one'. Three hours.

Global installs also mean global packages like 'ts-node' or 'nodemon' get installed against one specific Node version. Upgrade Node globally and those binaries can silently stop working or, worse, keep working but behave differently. NVM solves this by isolating each Node version in its own directory. When you switch versions, you switch the entire environment — runtime, npm, and all globally installed packages for that version.

NvmInstallSetup.bashBASH
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
# io.thecodeforge — JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# STEP 1: Install NVM using the official install script.
# This downloads NVM and wires it into your shell automatically.
# Always check https://github.com/nvm-sh/nvm for the latest version tag.
# As of writing, the current release is v0.39.7.
# ─────────────────────────────────────────────────────────────
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# ─────────────────────────────────────────────────────────────
# STEP 2: The install script appends these lines to your shell config.
# If you use bash, this goes into ~/.bashrc
# If you use zsh (default on modern macOS), this goes into ~/.zshrc
# You can verify by opening the file and looking at the bottom.
# ─────────────────────────────────────────────────────────────
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"          # This loads nvm itself
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # Tab-completion for nvm commands

# ─────────────────────────────────────────────────────────────
# STEP 3: Reload your shell config so the changes take effect
# in the CURRENT terminal session without opening a new window.
# Replace .zshrc with .bashrc if you're on bash.
# ─────────────────────────────────────────────────────────────
source ~/.zshrc

# ─────────────────────────────────────────────────────────────
# STEP 4: Verify NVM is installed and on your PATH.
# If this prints a version number, you're good.
# If it prints nothing, your shell config didn't load — see the warning callout.
# ─────────────────────────────────────────────────────────────
nvm --version
Output
0.39.7
Production Trap: 'nvm: command not found' After Install
If 'nvm --version' returns nothing or 'command not found', the install script found your shell config file but your terminal session hasn't reloaded it. Run 'source ~/.zshrc' (or 'source ~/.bashrc') manually, or just close and reopen the terminal. If it still fails, open ~/.zshrc and check the three NVM export lines are actually there at the bottom — the install script occasionally silently fails to append them if the file has a syntax error above.
Production Insight
If your team doesn't enforce .nvmrc, you'll eventually spend hours debugging version-specific errors during onboarding.
The biggest production risk isn't the version you choose—it's the version you don't know you're running.
Rule: always commit .nvmrc and run nvm use pre-build.
Key Takeaway
Global Node installs force you to choose one version for all projects.
The moment you need two versions, you're in uninstall-reinstall hell.
NVM isolates each version so you never have to choose.

Installing Node Versions and Switching Between Them

NVM has a clean mental model: you install versions into its own directory (~/.nvm/versions/node/), and at any moment exactly one version is 'active' in your current shell session. Switching is instant — no download, no restart, no package reinstall — because you're just changing which directory your PATH points to.

Here's the real-world scenario where this matters. You're maintaining a payment integration service that runs on Node 14 (the client hasn't approved an upgrade — you know how it goes). You also have a new internal tool you're building on Node 20 LTS. Without NVM, you are constantly uninstalling and reinstalling. With NVM, you install both once and switch in under a second.

One thing people miss: 'nvm use' only changes the version for the current terminal session. Open a new terminal tab and you're back to the default. That's intentional — it means you can have different projects open in different tabs, each running their own Node version simultaneously. The way to make a version sticky across all sessions is 'nvm alias default'.

NvmNodeVersionManagement.bashBASH
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
# io.thecodeforge — JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# See all available Node.js versions you can install.
# This list is long — pipe it through grep to filter for LTS versions,
# which are the ones you should be running in production.
# ─────────────────────────────────────────────────────────────
nvm ls-remote --lts

# ─────────────────────────────────────────────────────────────
# Install specific Node versions.
# You'll install as many as your projects need.
# LTS = Long Term Support — stable, security-patched, production-safe.
# ─────────────────────────────────────────────────────────────
nvm install 20          # Installs the latest Node 20.x.x release
nvm install 18          # Installs the latest Node 18.x.x release
nvm install 14.21.3     # Installs an exact patch version — useful when a
                        # specific patch fixed a bug your app depends on

# ─────────────────────────────────────────────────────────────
# See every version you have installed locally.
# The arrow (→) shows which one is currently active.
# ─────────────────────────────────────────────────────────────
nvm ls

# ─────────────────────────────────────────────────────────────
# Switch to a specific version for THIS terminal session only.
# This is the command you'll run most often.
# ─────────────────────────────────────────────────────────────
nvm use 18

# Confirm which version is now active
node --version
npm --version    # npm version changes too — it's bundled with Node

# ─────────────────────────────────────────────────────────────
# Set a default version that activates in every NEW terminal session.
# Without this, every new tab starts with no active version (or the
# last one you set as default — which might be wrong).
# ─────────────────────────────────────────────────────────────
nvm alias default 20

# ─────────────────────────────────────────────────────────────
# Jump to the version your current project's .nvmrc file specifies.
# This is the command you run when you cd into a project repo.
# If .nvmrc doesn't exist, this throws an error — see next section.
# ─────────────────────────────────────────────────────────────
nvm use
Output
# nvm ls output example:
# v14.21.3
# v18.20.2
-> v20.11.1
default -> 20 (-> v20.11.1)
lts/* -> lts/iron (-> v20.11.1)
# node --version after 'nvm use 18':
v18.20.2
# npm --version after 'nvm use 18':
10.5.0
Senior Shortcut: Install the Latest LTS in One Command
Instead of looking up the current LTS version number, run 'nvm install --lts' and NVM resolves it for you. Follow it with 'nvm alias default lts/*' to make the latest LTS your default automatically. This is the setup most senior devs run on fresh machines — no version-hunting required.
Production Insight
Running nvm use without --lts or .nvmrc means you might test on a Node version different from production.
That's how a deprecated API slips through CI and breaks at 2am deployment.
Rule: pin versions with .nvmrc and always test on your target version.
Key Takeaway
nvm install once, nvm use instantly.
Switching versions is free—no downloads, no restarts.
Your default should be the LTS that matches your main project.

The .nvmrc File: Make Your Whole Team Use the Right Node Version

Here's the part most tutorials skip, and it's the part that actually matters in a team setting. A .nvmrc file lives in the root of your project and contains exactly one thing: the Node version that project needs. When any developer runs 'nvm use' inside that directory, NVM reads the file and switches to the right version automatically. No Slack message. No README footnote nobody reads. No 'it works on my machine'.

This is particularly important for CI/CD pipelines. Every GitHub Actions or Jenkins job that checks out your repo should call 'nvm use' before running any Node commands. Otherwise your pipeline runs whatever Node version happens to be installed on the build agent — which can drift over time as agents get updated. I've seen a CI agent upgrade Node from 18 to 20 during a routine maintenance window and break a production build at 6am because one package used a deprecated API that Node 20 finally removed.

You can also configure NVM to auto-switch when you change directories. This requires a small addition to your shell config. It's optional, but once you try it you won't go back — you just 'cd' into a project and the right Node version is already active.

NvmRcProjectSetup.bashBASH
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
50
51
52
53
54
55
56
57
58
# io.thecodeforge — JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# CREATING A .nvmrc FILE FOR YOUR PROJECT
# Run this from your project root directory.
# This writes the current active Node version into .nvmrc.
# Commit this file — it belongs in source control.
# ─────────────────────────────────────────────────────────────
node --version > .nvmrc

# Verify what was written — should be something like 'v20.11.1'
cat .nvmrc

# ─────────────────────────────────────────────────────────────
# Or write a specific version manually:
# ─────────────────────────────────────────────────────────────
echo "20.11.1" > .nvmrc    # NVM accepts versions with or without the 'v' prefix

# ─────────────────────────────────────────────────────────────
# When any developer clones your repo and runs 'nvm use',
# NVM reads .nvmrc and switches to the right version.
# If they don't have it installed yet, they'll get:
#   N/A: version "20.11.1" is not yet installed.
# The fix: run 'nvm install' (no version arg) — it reads .nvmrc too.
# ─────────────────────────────────────────────────────────────
nvm install    # installs the version from .nvmrc if not already present
nvm use        # then activates it

# ─────────────────────────────────────────────────────────────
# AUTO-SWITCHING: make NVM change Node version automatically
# when you cd into a directory containing a .nvmrc file.
# Add this block to your ~/.zshrc (or ~/.bashrc for bash users).
# ─────────────────────────────────────────────────────────────

# Paste this into your shell config file:
autoload -U add-zsh-hook                    # zsh only — loads the hook utility

# This function runs every time you change directories
load-nvmrc() {
  local nvmrc_path
  nvmrc_path="$(nvm_find_nvmrc)"            # NVM's own function to find the nearest .nvmrc

  if [ -n "$nvmrc_path" ]; then             # if a .nvmrc was found...
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")  # resolve installed version

    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install                           # version not installed yet — install it
    elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
      nvm use                               # version installed but not active — switch
    fi
  elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ]; then
    nvm use default                         # left a .nvmrc directory — restore default
  fi
}

add-zsh-hook chpwd load-nvmrc               # wire the function to the directory-change hook
load-nvmrc                                  # also run it once on shell startup
Output
# cat .nvmrc:
v20.11.1
# When you cd into a project directory with .nvmrc containing 'v18.20.2':
Found '/path/to/your-project/.nvmrc' with version <v18.20.2>
Now using node v18.20.2 (npm v10.5.0)
# When you cd out to a directory without .nvmrc:
Restored to default Node version: v20.11.1
Interview Gold: .nvmrc vs engines field in package.json
Both express a required Node version, but they do different things. The 'engines' field in package.json is a documentation hint — npm warns about mismatches but doesn't enforce them unless you run 'npm install --engine-strict'. The .nvmrc file is an active enforcement tool — NVM reads it and physically changes the runtime. Use both: .nvmrc for local development switching, engines for CI enforcement.
Production Insight
A CI agent with a newer Node version can silently break your build after an OS update.
.nvmrc + nvm use in the pipeline prevents that drift without any manual intervention.
Rule: source .nvmrc in every pipeline step that runs Node commands.
Key Takeaway
Commit .nvmrc to every repo.
It's the cheapest team consistency tool you'll ever use.
Combine with auto-switching hook for zero overhead.

Managing Global Packages Per Node Version and Keeping Things Clean

Here's what surprises most developers switching to NVM for the first time: global packages don't carry over between Node versions. If you install 'npm install -g typescript' while on Node 20, then switch to Node 18, TypeScript is gone from that version's global scope. Each Node version in NVM has its own isolated node_modules directory for globals.

This is actually correct behaviour, not a bug. A TypeScript version compiled for Node 20 might behave differently on Node 14. Isolation prevents that silent mismatch. But it does mean you need to reinstall globals when you set up a new version. NVM has a shortcut for this: the '--reinstall-packages-from' flag copies all globally installed packages from one version to another during install.

Also know when to uninstall old versions. Unused Node versions sit in ~/.nvm/versions/ and each one takes around 50-100MB of disk space including its npm cache. After six months of grabbing versions you 'needed just to test something', you can easily have a gigabyte of stale Node installs. 'nvm uninstall' cleans them out. Run 'nvm ls' occasionally and prune anything you haven't touched in months.

NvmGlobalPackagesAndCleanup.bashBASH
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

# ─────────────────────────────────────────────────────────────
# REINSTALLING GLOBAL PACKAGES FROM ANOTHER VERSION
# When you install Node 20 and want all the globals you had on Node 18,
# pass the --reinstall-packages-from flag during install.
# NVM copies the package list from the source version and installs each one fresh.
# ─────────────────────────────────────────────────────────────
nvm install 20 --reinstall-packages-from=18

# ─────────────────────────────────────────────────────────────
# Check which global packages are installed for the CURRENT active version.
# This scope flag (-g) is crucial — without it you see project-local packages.
# ─────────────────────────────────────────────────────────────
npm list -g --depth=0

# ─────────────────────────────────────────────────────────────
# UNINSTALLING A NODE VERSION YOU NO LONGER NEED
# You cannot uninstall the version that's currently active.
# Switch to a different version first, then uninstall.
# ─────────────────────────────────────────────────────────────
nvm use 20                  # Switch away from the version you want to remove
nvm uninstall 14.21.3       # Remove Node 14.21.3 and all its global packages

# ─────────────────────────────────────────────────────────────
# ALIASING VERSIONS: give a version a memorable name
# Useful when you're jumping between a 'client-legacy' version
# and your standard dev version constantly.
# ─────────────────────────────────────────────────────────────
nvm alias payments-service 18.20.2   # create a named alias
nvm use payments-service             # switch using the alias name
nvm alias --delete payments-service  # remove the alias when done

# ─────────────────────────────────────────────────────────────
# CHECKING WHICH VERSION A PROJECT SHOULD USE
# before running 'nvm use', see what version .nvmrc specifies
# without actually switching. Useful in scripts.
# ─────────────────────────────────────────────────────────────
cat .nvmrc

# ─────────────────────────────────────────────────────────────
# FULL CLEANUP: see how much disk space your NVM installations are using
# ─────────────────────────────────────────────────────────────
du -sh ~/.nvm/versions/node/*    # shows size of each installed version
Output
# npm list -g --depth=0 on Node 20 after reinstall from Node 18:
/home/user/.nvm/versions/node/v20.11.1/lib
├── npm@10.5.0
├── typescript@5.3.3
└── nodemon@3.0.3
# nvm uninstall 14.21.3:
Uninstalled node v14.21.3
# du -sh output example:
98M /home/user/.nvm/versions/node/v14.21.3
112M /home/user/.nvm/versions/node/v18.20.2
108M /home/user/.nvm/versions/node/v20.11.1
Never Do This: Install Global CLI Tools Without Checking Active Version
Running 'npm install -g some-cli-tool' without confirming which Node version is active means the tool gets installed into the wrong version's global scope. You'll run the tool later, it'll fail with 'command not found', and you'll spend 20 minutes debugging PATH issues. Always run 'nvm current' or 'node --version' first. One second of checking saves twenty minutes of confusion.
Production Insight
Installing a global CLI tool on the wrong Node version leads to command-not-found errors that look like PATH issues.
Always check nvm current before npm install -g.
Rule: prefer project-local devDependencies over global installs.
Key Takeaway
Global packages are per-version.
Use --reinstall-packages-from when installing new versions.
Prune unused Node versions monthly—they accumulate 100MB each.

Advanced NVM: Aliases, CI/CD Integration, and Keeping Your Setup Clean

Once you've mastered basic switching, there are a few power tools that make NVM sing in a team and CI environment.

Named Aliases for Frequent Versions You don't have to remember version numbers. If you switch between a legacy project (Node 14) and a modern one (Node 20) every day, create aliases: nvm alias legacy 14.21.3 nvm alias modern 20.11.1 Then switch with 'nvm use legacy' or 'nvm use modern'. Aliases are stored in ~/.nvm/alias/ and persist across sessions.

Using NVM in CI/CD In GitHub Actions, you can use the official 'actions/setup-node@v4' action which handles version pinning from .nvmrc. But if you're using NVM directly, add these steps to your pipeline YAML: - name: Setup NVM and Node run: | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm install nvm use Be careful: each step in a CI pipeline is a new shell – you must load NVM in every step that uses Node or merge the Node command into one step.

Docker and NVM You can install NVM inside a Docker container for local dev, but for production images, use the official Node images with a specific tag (e.g., 'node:20.11.1-slim'). NVM inside a container adds unnecessary layers and startup time. The rule: NVM for host machines, official Node images for deployment.

Cleaning Up Run 'nvm cache clear' to free up cached npm packages across all versions. Use 'nvm prune' to remove removed versions' npm caches. And don't forget to periodically purge old versions with 'nvm uninstall [version]'.

List All Remote LTS Versions Use 'nvm ls-remote --lts' to see all LTS releases. Great for assessing which versions are still supported for your projects.

NvmAdvancedPatterns.bashBASH
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
50
# io.thecodeforge — JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# CREATING NAMED ALIASES
# ─────────────────────────────────────────────────────────────
nvm alias payments-service 18.20.2
nvm use payments-service   # switches to Node 18.20.2

# ─────────────────────────────────────────────────────────────
# GITHUB ACTIONS STEP USING NVM DIRECTLY (single-step approach)
# ─────────────────────────────────────────────────────────────
# In your workflow YAML:
# - name: Use correct Node version
#   run: |
#     curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
#     export NVM_DIR="$HOME/.nvm"
#     [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
#     nvm install
#     nvm use
#     node --version

# ─────────────────────────────────────────────────────────────
# DOCKERFILE SNIPPET: Using NVM for development container
# For production, use versioned official images instead.
# ─────────────────────────────────────────────────────────────
# FROM ubuntu:22.04
# RUN apt-get update && apt-get install -y curl
# RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# ENV NVM_DIR /root/.nvm
# RUN \
#   . $NVM_DIR/nvm.sh && \
#   nvm install 20 && \
#   nvm alias default 20

# ─────────────────────────────────────────────────────────────
# CLEAN UP CACHES AND OLD VERSIONS
# ─────────────────────────────────────────────────────────────
nvm cache clear                    # clear npm cache for all versions
nvm prune                          # remove npm caches for uninstalled versions
nvm uninstall 14.21.3              # remove version 14

# ─────────────────────────────────────────────────────────────
# LIST ALL REMOTE LTS VERSIONS
# ─────────────────────────────────────────────────────────────
nvm ls-remote --lts

# ─────────────────────────────────────────────────────────────
# SHOW CURRENT NODE VERSION (quick check)
# ─────────────────────────────────────────────────────────────
nvm current
Output
# nvm alias payments-service 18.20.2
Creating alias payments-service -> 18.20.2
# nvm use payments-service
Now using node v18.20.2 (npm v10.5.0)
# nvm current after alias use:
v18.20.2
# nvm cache clear:
Caches cleared.
# nvm prune:
Pruned garbage.
Senior Pattern: Use nvm in Docker for Reproducible Dev, Not Production
For local development containers, installing NVM gives you the same flexibility as your host machine. But for production Docker images, use the official Node image with a specific version tag – it's smaller, faster, and removes the risk of NVM installation issues. NVM's power is for the developer's machine, not the deployment target.
Production Insight
In Docker, running nvm within the container adds unnecessary layers and startup time.
Use official Node images with version tags for deployment.
Rule: Docker for deployment, NVM for development.
Key Takeaway
NVM aliases make switching between project versions memorable.
For CI, nvm use .nvmrc is the only command you need.
For Docker, use versioned Node images, not NVM inside containers.

The 'Before NVM' Nightmare — And Why We're Glad It's Over

You haven't felt true dread until you've had to downgrade Node globally because a legacy CRM's build pipeline hardcodes require('crypto') and breaks under Node 18. Or when npm install starts vomiting peer dependency warnings because your CI agent and your local machine are running different Node majors. That's the 'before NVM' life — one global Node binary, one set of system-wide dependencies, and zero room for project-specific sanity.

This isn't just inconvenience. It's a silent contract that your dev environment will eventually diverge from production. You fix a bug locally, push to staging, and the deployment fails because the runtime environment uses a different V8 engine version. The fix? 'Just use nvm use system' — a command nobody actually runs before pushing. The real fix is treating Node versions as project configuration, not personal preference.

NVM kills that nightmare by making Node installation per-user, per-shell, and per-directory. No sudo, no global conflicts, no 'works on my machine' excuses. You declare the version once in .nvmrc and every team member inherits the exact same runtime. If it breaks, it breaks for everyone — which is a feature, not a bug.

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

// Before NVM: one global Node, one set of problems
const fs = require('fs');

class LegacyApiClient {
  constructor() {
    // Node 10's Buffer API — crashes on Node 18+
    this.buffer = new Buffer(1024); // DEPRECATED
  }

  fetchData() {
    // process.binding('crypto') — removed in Node 17
    const crypto = process.binding('crypto');
    return crypto.randomBytes(32);
    // ^^ immediate segfault on Node 18+
  }
}

const client = new LegacyApiClient();
console.log('Client initialized with global Node:', process.version);
// Output: Client initialized with global Node: v18.17.0
// Then: Error: process.binding is not supported in this runtime
Output
Client initialized with global Node: v18.17.0
Error: process.binding is not supported in this runtime
Senior Shortcut:
If you see 'process.binding' or 'new Buffer()' in a codebase older than 2019, that module is a ticking time bomb. Don't patch it — pin the Node version in .nvmrc and kill the module at your next sprint.
Key Takeaway
Global Node installs are a ticking clock for CI breakage. Pin your runtime per project, not per machine.

Setting the Stage: Prerequisites for NVM Glory

Before you install NVM, audit your current Node situation. Run which node and node --version. If you see a path like /usr/local/bin/node, you've got a system-level installation that's been polluting your environment since you first brew install node or downloaded the macOS installer. That thing has to go — or at least get out of the way.

Uninstall system Node first. On macOS, brew uninstall --force node and delete /usr/local/lib/node_modules. On Linux, apt remove node or yum remove nodejs. Windows folks, use the official installer's 'Remove' option. Then delete any remaining .npmrc or .node_repl_history from your home directory. You want a clean slate — no version envy, no legacy symlinks polluting your path.

Next, pick your shell. NVM installs a shell function that overrides node, npm, and npx. If you're using zsh, bash, or fish, you're fine. If you're on Windows, use nvm-windows (a separate project, not the same NVM). Do NOT use sudo during NVM installation — it's a user-level tool. Run curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash and let it append the source command to your .bashrc or .zshrc. Restart your terminal. Then verify: command -v nvm should return nvm.

SetupAudit.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
// io.thecodeforge — javascript tutorial

// Step 1: Check current Node state before NVM
const { execSync } = require('child_process');

function auditNode() {
  try {
    const nodePath = execSync('which node').toString().trim();
    const nodeVersion = execSync('node --version').toString().trim();
    const npmPath = execSync('which npm').toString().trim();
    
    console.log('Current Node path:', nodePath);
    console.log('Node version:', nodeVersion);
    console.log('npm path:', npmPath);
    
    // Warn if system-level installation found
    if (nodePath.includes('/usr/local/') || nodePath.includes('/usr/bin/')) {
      console.warn('WARNING: System-level Node detected. Uninstall before NVM.');
    } else {
      console.log('OK: No system-level Node found.');
    }
  } catch (err) {
    console.log('No Node installation detected — clean slate!');
  }
}

auditNode();
Output
Current Node path: /usr/local/bin/node
Node version: v18.17.0
npm path: /usr/local/bin/npm
WARNING: System-level Node detected. Uninstall before NVM.
Production Trap:
Many install guides skip the uninstall step. Do NOT skip it. A leftover system Node will shadow NVM's binaries and cause 'nvm ls' to show the wrong default. Just nuke the global Node first.
Key Takeaway
Clean your system of global Node before installing NVM. A dirty slate leads to path conflicts that waste hours.

The Core of NVM: Essential Commands That Pay Your Rent

You don't need to memorize every NVM flag. You need five commands that solve 90% of real-world problems: nvm install, nvm use, nvm ls, nvm alias, and nvm run. Memorize these like your production deployment checklist.

nvm install <version> downloads and compiles Node from source (or binaries if available). Always use the LTS release: nvm install --lts. For specific projects, nvm install 16.20.2 — note the full semver. NVM caches compiled binaries, so subsequent installs of the same version are instant.

nvm use <version> switches the active Node version in the current shell. This is where .nvmrc shines: nvm use reads the file automatically. Without it, you're typing nvm use 18.17.0 every time you cd into a project — which nobody does consistently.

nvm ls lists all installed versions. Green means active. Now, for the hero command: nvm alias default <version>. This sets your base Node version for new shells. Never rely on 'latest' as default because 'latest' changes silently. Pin to an LTS: nvm alias default lts/hydrogen (or just 18 for the LTS codename).

Finally, nvm run <version> app.js runs a script with a specific Node version without switching your shell — perfect for CI or forking builds. Combine with --silent to suppress NVM's splash output in automated scripts.

CoreCommands.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
// io.thecodeforge — javascript tutorial

// Common NVM commands every dev must know

// List installed versions
const { execSync } = require('child_process');

console.log('=== Current NVM status ===');

try {
  // Equivalent to nvm ls
  const versions = execSync('nvm ls').toString();
  console.log('Installed versions:\n', versions);

  // Show current active version
  const active = execSync('nvm current').toString().trim();
  console.log('Active version:', active);

  // Run a script with a specific version (no switch)
  const output = execSync('nvm run 18.17.0 --version 2>&1').toString();
  console.log('Running Node 18.17.0:', output);

} catch (err) {
  console.error('NVM not installed or shell function missing:', err.message);
}

// Example .nvmrc content (stored in project root)
// Write: echo "18.17.0" > .nvmrc
// Then: nvm use # reads .nvmrc automatically
Output
=== Current NVM status ===
Installed versions:
v16.20.2
v18.17.0
-> v18.17.0
Active version: v18.17.0
Running Node 18.17.0: v18.17.0
Senior Shortcut:
Add nvm use to your shell's cd hook. In zsh, put this in .zshrc: autoload -U add-zsh-hook; add-zsh-hook chpwd nvm_auto (then define a function that checks for .nvmrc). This auto-switches versions when you enter a project directory. No more 'wait, we're on Node 16?' surprises.
Key Takeaway
Five NVM commands handle 90% of version management: install, use, ls, alias, run. Pin your default to an LTS and use .nvmrc for project-specific versions.

NVM's Hidden Superpowers: Stop Copy-Pasting Commands Like a Noob

You already know nvm install and nvm use. That's the basics. But NVM has a toolbox of advanced features that will save your ass on a Friday afternoon deployment. Aliases let you name a Node version something meaningful like default or lts/argon. Stop typing nvm use 18.17.0 every time — alias it to lts or stable.

CI/CD pipelines love NVM. Script nvm install in your Dockerfile and pin the exact version. No surprises when the build agent has a different Node. Combine with .nvmrc so your pipeline auto-magically picks the right version. For monorepos, you can have multiple .nvmrc files per directory — NVM respects the one closest to your working directory.

Pro tip: nvm current tells you what's active. nvm alias default 18.17.0 sets your shell startup version. nvm unalias cleans up orphans. These commands pay rent. Use them.

ci-pipeline-nvm.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — javascript tutorial

// CI/CD Dockerfile snippet — pin Node version
const { execSync } = require('child_process');

function nvmSetup() {
  // Install NVM and activate specific version
  execSync('curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash', { stdio: 'inherit' });
  execSync('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"', { stdio: 'inherit' });
  execSync('nvm install 18.17.0', { stdio: 'inherit' });
  execSync('nvm alias default 18.17.0', { stdio: 'inherit' });  // Pin for consistent builds
  console.log('Node version locked:', execSync('node --version').toString().trim());
}

nvmSetup();
Output
Node version locked: v18.17.0
Senior Shortcut:
Use nvm current in your shell prompt to always see the active Node version. Prevents "which Node?" confusion during troubleshooting.
Key Takeaway
Aliases and CI integration aren't optional — they're your safety net for reproducible builds.

The "Buts" and "What Ifs": Why NVM Isn't a Silver Bullet

Let's be honest: NVM is great, but it has warts. First, it's a shell function, not a binary. That means every new terminal session reloads NVM — adds ~200ms to startup. If you're on Windows via WSL, expect quirks with path resolution and permission errors. NVM doesn't play nice with some package managers like pnpm or yarn if they cache global binaries per Node version.

Another pain: NVM doesn't auto-detect .nvmrc in subdirectories of a monorepo. You have to manually call nvm use after cd. There's also the "version drift" problem — your CI might run NVM v0.39, but devs have v0.38, causing subtle differences in how NVM resolves aliases.

The ugly truth: If you're on macOS with Homebrew, nvm installs as a shell function, breaking if you switch shells mid-session. And if you need multiple Node versions running simultaneously (e.g., two servers on different ports), NVM can't help — that's a Docker job. Know the limits before you blame the tool.

nvm-wsl-workaround.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — javascript tutorial

// WSL path quirk fix — force NVM to use correct node version
const { execSync } = require('child_process');
const path = require('path');

function fixWslNvmPath() {
  const nvmDir = path.join(process.env.HOME, '.nvm');
  const nodePath = path.join(nvmDir, 'versions', 'node', 'v18.17.0', 'bin');
  process.env.PATH = `${nodePath}:${process.env.PATH}`;  // Prepend, not append
  const version = execSync('node --version').toString().trim();
  console.log('NVM workaround active, Node:', version);
}

fixWslNvmPath();
Output
NVM workaround active, Node: v18.17.0
Production Trap:
If you use nvm exec inside a npm script, the child process inherits your shell. Double-check NVM's $PATH resolves before the system Node — or CI will silently use the wrong version.
Key Takeaway
NVM is a shell tool, not a container. It excels at version switching — not multi-process concurrency or cross-platform magic.

The Beauty of NVM: Advantages That Make You Go "Wow!"

NVM delivers isolation without container overhead. Each Node version gets its own global space, preventing the nightmare of a globally installed package breaking across projects. Switch versions in one command — no uninstalling, no path hacking. The real wow moment? Your CI pipeline matches your local .nvmrc exactly. No more "works on my machine" because the Node version is identical. NVM also lets you test edge versions safely: run nvm run 18.10.0 app.js without touching your default. It downloads binaries, not source compilations, making version swaps sub-second. For teams, nvm install reads .nvmrc automatically — new hires are productive in one command. The advantage is precision: every environment, from dev to prod, runs the exact same engine. That control is the beauty.

nvm-version-isolation.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
// io.thecodeforge — javascript tutorial

// Test a specific Node version without altering default
const { execSync } = require('child_process');
const version = '18.10.0';

// nvm run executes the script with that version only
execSync(`nvm run ${version} -e "console.log(process.version)"`, {
  stdio: 'inherit'
});

// Default remains untouched
console.log('Default version:', process.version);
Output
v18.10.0
Default version: v20.11.0
Production Trap:
Switching Node versions in production is risky. NVM is for dev and CI. In production, pin your Node version via Docker or the runtime provider — never rely on nvm use in production scripts.
Key Takeaway
NVM gives you exact version control per project, enabling zero-conf team onboarding and safe edge testing.

The Road Ahead: Continuous Improvement and Community

NVM is not dormant. The community actively patches support for new Node releases, resolves shell compatibility (bash, zsh, fish), and integrates with modern tooling like corepack and Volta. Upcoming focus: faster binary downloads, Windows native support (via nvm-windows), and seamless .nvmrc integration with IDEs like VSCode. The road ahead includes async version resolution to avoid shell startup slowdowns. Contributions are welcome — reporting broken mirrors, adding new architectures (ARM64), and improving CI caching. The project's GitHub issues are a goldmine for learning version management pitfalls. As Node evolves (experimental features, ESM changes), NVM will adapt. The community's health ensures NVM stays relevant against alternatives. Your .nvmrc file today will work tomorrow — that's the promise of active maintenance.

nvm-ci-cache.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
// io.thecodeforge — javascript tutorial

// CI script to cache NVM installations
const fs = require('fs');
const version = fs.readFileSync('.nvmrc', 'utf8').trim();

// Only install if not cached
if (!fs.existsSync(`/opt/nvm/versions/node/v${version}`)) {
  execSync(`nvm install ${version}`, { stdio: 'inherit' });
}
execSync(`nvm use ${version}`, { stdio: 'inherit' });
console.log(`CI running Node ${process.version}`);
Output
CI running Node v18.10.0
Community Tip:
Watch the NVM GitHub repo's Releases tab. When Node drops a major version, NVM typically supports it within 48 hours. Subscribe to the RSS feed for updates.
Key Takeaway
NVM is actively maintained — your .nvmrc will keep working as Node evolves, backed by a responsive community.

Conclusion: NVM as a Standard, Not a Luxury

After years of managing Node.js environments across dozens of projects, NVM has become non-negotiable. Its true value emerges not from convenience alone, but from eliminating the silent failures caused by version mismatches in production and CI. The .nvmrc file, once adopted by your team, enforces consistency without manual intervention. Aliases and per-version global packages prevent the slow decay of local environments. NVM does not solve every problem — legacy systems and Docker-based workflows still demand care — but it replaces chaos with predictable control. For any team shipping JavaScript to production, asking "which Node version does this run on?" should never be a guess. NVM forces the answer to be explicit and reproducible. This is not about tools; it is about reducing the mental overhead of environment management so you can focus on code that matters. Treat NVM as infrastructure, not an afterthought.

nvm-verify-production.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
// io.thecodeforge — javascript tutorial
// Verifying your project Node version against .nvmrc
import { readFileSync } from 'fs';
const nvmrc = readFileSync('.nvmrc', 'utf8').trim();
const current = process.version.slice(1);
if (current !== nvmrc) {
  console.error(`Node mismatch: expected ${nvmrc}, found ${current}`);
  process.exit(1);
}
console.log('Node version verified.');
Output
Node version verified.
Production Trap:
Running 'nvm use' in production is dangerous. Instead, lock Node version in Dockerfile or CI. NVM is for local development; production needs deterministic builds.
Key Takeaway
Adopt NVM as a team standard. Combine .nvmrc with automated version checks in CI to prevent silent version drift.

About Author

Vishal Jagtap writes for TheCodeForge.io, where he dissects JavaScript tooling with a focus on production-grade workflows. With over a decade of experience scaling frontend and backend systems, he has learned that the best code often lives outside the source files — in how you manage dependencies, versions, and environments. Vishal advocates for boring, reliable tooling over flashy abstractions. When he is not debugging Node version mismatches at 2 AM, he contributes to open-source projects that make developers' lives simpler. His writing avoids fluff and targets the decisions that reduce long-term maintenance cost. Follow him on HackerNoon for no-nonsense tutorials on JavaScript, DevOps, and developer experience.

Key Takeaway
Reliable tooling beats complexity. Vishal teaches pragmatic environment management for shipping production JavaScript.
● Production incidentPOST-MORTEMseverity: high

The 6am CI Pipeline That Broke Because the Build Agent Updated Node

Symptom
During a routine CI maintenance window, the build agent was updated from Ubuntu 20.04 to 22.04, which shipped with Node 20 instead of Node 18. The next production build failed with 'Error: crypto.createCipher is not a function' at 6am.
Assumption
The team assumed the CI pipeline would always use the same Node version because Docker wasn't used for builds. They didn't pin Node explicitly.
Root cause
The build agent's global Node version was used by the pipeline. After the OS update, Node 20 became the default. The pipeline had no explicit Node version pinning via .nvmrc or nvm use.
Fix
Add nvm install and nvm use to the pipeline script before any Node commands. Also, pin the Node version in the Docker image used for CI, or use the official Node Docker image with a specific tag.
Key lesson
  • Always pin Node versions in CI pipelines explicitly—never rely on the build agent's default.
  • Use .nvmrc in your repo and run nvm use as the first step in every pipeline step that runs Node.
  • Containerized CI agents (Docker) prevent OS-level drift from affecting your builds.
Production debug guideSymptom -> Action for the most common NVM issues4 entries
Symptom · 01
nvm: command not found
Fix
Reload shell config: source ~/.zshrc or ~/.bashrc. If still missing, check that the NVM export lines are at the bottom of the file. The install script may have failed if the file had a syntax error.
Symptom · 02
nvm use 20 gives 'N/A: version not installed'
Fix
Run nvm install 20 first. nvm use does not install—it only switches to already installed versions.
Symptom · 03
node --version still shows old version after nvm use
Fix
Run which node. If it shows /usr/bin/node instead of ~/.nvm/versions/node/..., you have a system Node install on PATH that takes precedence. Uninstall system Node or verify NVM's bin dir is earlier in PATH.
Symptom · 04
Global npm packages missing after switching Node versions
Fix
Each Node version has its own global scope. Run npm list -g --depth=0 to see current version's globals. Use nvm install [new] --reinstall-packages-from=[old] to copy packages when installing a new version.
★ NVM Quick Debug Cheat SheetFast commands to diagnose and fix the most common NVM issues.
nvm command not found
Immediate action
Run `source ~/.zshrc` or `source ~/.bashrc`
Commands
echo $NVM_DIR (should output ~/.nvm)
cat ~/.zshrc | tail -5 (verify NVM load lines)
Fix now
Add missing NVM export lines manually or re-run install script.
Version not installed when running nvm use+
Immediate action
Run `nvm install` (reads .nvmrc) or `nvm install [version]`
Commands
nvm ls (list installed versions)
cat .nvmrc (check expected version)
Fix now
Install the required version with nvm install.
Global package not found after version switch+
Immediate action
Run `nvm use [previous version]` to get it back
Commands
npm list -g --depth=0 (check current globals)
nvm install [new] --reinstall-packages-from=[old]
Fix now
Reinstall with nvm install [new] --reinstall-packages-from=[old] or migrate to project-local devDependencies.
NVM vs Global Node Install
Feature / AspectGlobal Node Install (nodejs.org)NVM
Multiple Node versions simultaneouslyNo — one version at a time, globallyYes — unlimited versions, all isolated
Switching versionsUninstall and reinstall — 5-10 minutesnvm use 18 — under 1 second
Per-project version pinningNot possible without manual PATH hacksAutomatic via .nvmrc file
Global package isolationOne shared global scope for all projectsEach Node version has its own global scope
CI/CD compatibilityDepends entirely on the build agent's Node versionnvm install + nvm use in pipeline script
Windows supportFull native supportNVM for Unix/macOS only — use nvm-windows on Windows
Team consistency enforcementREADME note that nobody reads.nvmrc committed to git — enforced automatically
Requires sudo/admin rightsYes — installs into system directoriesNo — installs entirely in your home directory
Rollback to previous versionFull uninstall + reinstall requirednvm use [previous-version] — instant
Disk footprintSingle install, minimal~100MB per version — prune unused ones regularly

Key takeaways

1
NVM doesn't just switch Node versions
it switches entire environments. npm version, global packages, and native module binaries all change with it. One 'nvm use' call changes everything, which is exactly why it's powerful and why you need to know what's active before installing anything globally.
2
The .nvmrc file is the difference between 'works on my machine' and 'works on every machine'. Commit it to every repo. It costs nothing and saves your team from version mismatch debugging sessions that always happen at the worst possible time.
3
Global packages don't survive version switches. If your team suddenly can't find 'tsc' or 'nodemon' after a version switch, that's why. The fix is '--reinstall-packages-from' on install, or better yet, putting CLI tools in devDependencies so they're project-local and version-agnostic.
4
The counterintuitive truth
NVM installs entirely in your home directory with zero sudo privileges required. That's not a quirk — it's intentional. System-level Node installs require admin rights and pollute shared system directories. NVM keeps everything in ~/.nvm and that's the right design for a per-user tool.

Common mistakes to avoid

4 patterns
×

Running nvm use in a subshell or script expecting it to change the parent shell

Symptom
After running the script, the terminal still shows the old Node version. The script's nvm use command only affects the subshell it runs in.
Fix
Either source the script (source ./script.sh) instead of executing it, or use the nvm-use command in the shell profile. For CI scripts, ensure the entire pipeline runs in the same shell session.
×

Assuming nvm use persists across new terminal sessions

Symptom
After opening a new terminal tab, the Node version returns to default, causing version mismatch errors when running Node commands.
Fix
Run nvm alias default [version] to persist the version. Better: use .nvmrc + auto-switching so every project sets itself up automatically.
×

Not removing system Node before installing NVM

Symptom
nvm --version works, but which node shows /usr/bin/node (system path) instead of ~/.nvm/versions/... NVM is not taking precedence.
Fix
Uninstall system Node entirely (sudo apt remove nodejs on Ubuntu, or uninstall via package manager). Then reload shell. NVM should be the only Node on PATH.
×

Committing node_modules built under one Node version and deploying to a different version

Symptom
Native addons like bcrypt or sharp throw 'The module was compiled against a different Node.js version' error when the server runs a different Node version.
Fix
Never commit node_modules. Add node_modules to .gitignore. Always run npm ci (clean install) on the target server after switching to the correct Node version via .nvmrc.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
If two developers on your team both run 'nvm use' in the same project di...
Q02SENIOR
Your CI pipeline runs 'nvm use' inside a shell script but the Node versi...
Q03SENIOR
When would you choose a Docker-based Node version strategy over NVM in a...
Q04JUNIOR
A junior developer reports that 'nodemon' suddenly stopped working after...
Q01 of 04SENIOR

If two developers on your team both run 'nvm use' in the same project directory but get different active Node versions, what are the possible causes and how do you diagnose it?

ANSWER
Possible causes: (1) Each developer has a different default alias set. (2) The .nvmrc file is not committed or is inconsistent. (3) One developer is using the auto-switching hook and the other isn't. (4) They have different versions installed – one has the version from .nvmrc, the other doesn't and falls back to default. Diagnosis: Have each developer run 'cat .nvmrc' to compare the file, then 'nvm ls' to see installed versions, and 'nvm alias default' to see their default. The fix is to ensure .nvmrc is committed and all developers run 'nvm install' then 'nvm use' after cloning.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Does NVM work on Windows?
02
What's the difference between 'nvm install' and 'nvm use'?
03
How do I make NVM automatically switch Node versions when I cd into a project folder?
04
Why do global npm packages disappear when I switch Node versions with NVM?
05
Should I use NVM inside a Docker container?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Drawn from code that ran under real load.

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

That's Node.js. Mark it forged?

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

Previous
Node.js Clustering
16 / 18 · Node.js
Next
Nodemon: Auto-Restart Node.js Apps During Development