Junior 6 min · March 28, 2026

NVM Node Version Manager — Stop OS Node Updates Breaking CI

Ubuntu 22.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
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.
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 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.
● 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?
🔥

That's Node.js. Mark it forged?

6 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