From a Version Request to an Installed Runtime
For nvm-sh/nvm, the hard part of installation begins after a version description has been understood: the manager must decide what local state can satisfy it, what must come from a mirror, and whether the resulting files are safe to activate as an installation. This chapter follows that lifecycle so the reader can choose an online, offline, binary, or source-oriented path and diagnose a failure by stage rather than treating nvm install as one opaque download.
The lifecycle has two different kinds of state. A request and its resolved version are logical state. A cached archive, an extracted directory, and an active shell are physical or process state. Nvm moves between them in order, but it does not treat one as proof of the next. A valid request can have no local match. A downloaded archive can fail verification. An extracted tree can be incomplete. An installed tree can exist without yet being the version the current shell will execute.
Resolution does not fetch the runtime yet
A normal online installation starts by resolving the requested description against release metadata. If the user supplied a complete version, the task is to identify that release. If the request was partial, an alias, or an LTS label, nvm first obtains the concrete version that the current metadata supports. That result determines the artifact name, platform and architecture suffix, compression format, and eventual installation directory.
This separation gives the command a useful failure vocabulary. If the description cannot be resolved, nvm stops before it has an artifact to download. If it resolves but the chosen platform has no compatible binary, the failure belongs to acquisition policy rather than to version syntax. If an archive arrives but its checksum does not match, the problem is with the downloaded material or its metadata. Keeping those outcomes distinct makes recovery more disciplined: changing a version selector will not repair a corrupt archive, and retrying a download will not repair an invalid project file.
The online path normally consults two classes of remote information. A release index tells nvm which versions exist and, for Node.js, which LTS line a release belongs to. A checksum file describes the expected digest for a particular artifact. Nvm uses the index to resolve a request and the checksum information to check the archive it is about to unpack. The two lookups serve different purposes even when they come from the same release host.
The local cache is considered before a fresh download is accepted. A cached archive is useful only if it corresponds to the requested artifact and its checksum matches the expected value. When it matches, nvm can reuse it, avoiding bandwidth and latency. When it does not, nvm removes the broken cached archive rather than quietly extracting it. This makes the cache a performance and availability layer, not a second source of unexamined truth.
The offline path changes the inputs and the guarantees deliberately. With --offline, nvm does not attempt to contact a mirror. It first considers installed versions and then cached downloads, resolving the request from those local candidates. A readable cached archive can be used without the online checksum lookup or a new download. If neither installed state nor cache can satisfy the request, the command reports that the version is unavailable locally instead of silently going online.
That mode is valuable in an air-gapped environment or when a build must not depend on current network availability. It also moves responsibility to the local cache. The fact that an archive was cached earlier is not the same as proving its origin at the moment of offline installation. Offline operation therefore trades freshness and network independence against the opportunity to retrieve current metadata and perform the normal online checksum comparison.
The normal online checksum path is stronger than a blind cache hit, but its meaning must be stated precisely. Nvm selects an available checksum utility, prefers SHA-256-capable tools, downloads the expected value, computes a digest of the archive, and rejects a mismatch before extraction. The repository also retains compatibility fallbacks for older environments. If the host cannot produce a computed digest, the implementation has a warning path that can continue without verification. The presence of a checksum-related step in the code is therefore not a guarantee that every host has performed the same cryptographic check.
The checksum comparison also does not answer the authenticity question by itself. A digest can reveal a truncated or corrupted transfer and can frustrate an intermediary that cannot alter both the payload and the expected value. It does not prove that a mirror is honest if the mirror controls the payload and the checksum metadata together. That distinction belongs to the trust boundary around installation, but it already matters here because “verified” must mean verified against a particular piece of metadata, not “certified safe by nvm.”
The acquisition path also validates inputs that will become network arguments or filesystem names. Nvm rejects version strings containing characters outside the forms it expects before constructing the artifact path. It validates mirror URLs and passes downloader arguments as literal arguments rather than evaluating a string as shell code. These checks make it possible to treat release metadata as input to a parser without allowing a malformed version to change the parser itself. They do not change the fact that the selected artifact is intended to be executed later.
The full selection and acquisition relationship is easier to follow as a sequence:
The diagram deliberately ends at validation. Installation is not complete merely because bytes have been written somewhere. The lifecycle still has to decide whether the tree has the expected shape and whether the shell should select it.
Acquisition, validation, and activation are separate states
When a compatible precompiled binary is available, nvm downloads an archive, extracts it into a temporary location, and prepares the version-specific tree. Extraction removes the archive’s leading directory layer so the resulting files fit the layout nvm expects. A successful extraction is only an intermediate state: it says the archive could be unpacked, not that the installation is complete or runnable.
Nvm attempts to replace a target version directory as one operation after extraction has succeeded. That matters when a previous attempt left a nonempty but broken directory behind. Merging a fresh tree into that directory can preserve stale files or fail when existing subdirectories block a move. Removing the old target and renaming the prepared tree avoids exposing a half-updated mixture on filesystems where a single rename is possible. If the temporary directory and the destination are on different filesystems, nvm retains a per-entry move fallback; that is a compatibility compromise, not the same atomic boundary as one rename.
A binary may be unavailable for the host even when the version itself is real. The operating system, CPU architecture, libc implementation, and release all influence the artifact nvm can request. On Alpine, for example, the relevant binary may need a musl-compatible build. Older releases or less common architectures may have no official binary at all. Nvm normally treats that absence as a reason to try a source build, unless the user requests binary-only behavior with -b or sets NVM_NO_SOURCE_FALLBACK=1 as a persistent policy.
A source installation has a different lifecycle and a different cost. Nvm downloads and extracts source, enters the extracted tree, runs the project’s configure step with the requested installation prefix, and invokes the platform’s build tool before installing the result. This requires a suitable compiler and build environment, can take substantially longer than extracting a binary, and is not supported on non-WSL Windows by the project’s own path. The -s option makes the source choice explicit. It is useful when a binary is absent or incompatible, but it also means the local machine executes the source project’s build commands.
The choice among binary, source, and offline operation is therefore a policy decision:
- Binary installation favors speed and the build artifacts published for the host.
- Source installation favors the possibility of supporting a platform or release without a matching binary, at the cost of toolchain setup and build time.
- Offline installation favors network independence and repeat use of local material, at the cost of current metadata and the normal online checksum retrieval.
- Binary-only or no-source-fallback policy favors predictable failure over an unexpectedly long local compile.
None of these modes is simply “more reliable.” Reliability depends on what failure the user can tolerate. A CI job with a strict time budget may prefer to fail when no binary exists. A developer on an unsupported architecture may accept a source build. An isolated environment may have no choice but to prepare and carry a cache in advance.
Nvm adds a per-version advisory lock around the ordinary installation path. The lock is represented by a directory created with an atomic mkdir operation under the cache. If another nvm process is installing the same version, the second process waits rather than allowing both processes to remove, populate, or replace the same version tree at once. Installs of different versions use different lock names and can proceed independently. A timeout reports the lock path, while stale-lock removal is opt-in so a slow live build is not mistaken for a crashed one by default.
The lock protects a narrow race, not every possible race. It coordinates nvm’s own install operations for one version. It does not coordinate an unrelated tool that edits the same directory, protect against a malicious same-user process, or make source compilation atomic. Its purpose is more modest and more concrete: prevent two cooperating nvm installs from treating the same version directory as independent workspaces.
After extraction or source installation, nvm performs a shape check before activation. The expected version directory must exist, bin/node must be nonempty and executable, and a present npm symlink must not point at a missing target. This check intentionally does not execute Node.js to decide whether the installation is valid. A binary can be correctly installed yet fail to run on an incompatible host, while a corrupt archive should already have been rejected by checksum handling. The validator is checking whether nvm’s expected installation structure exists, not promising that every host can execute every release.
That distinction prevents a particularly damaging success state. Without a shape check, a zero-byte executable file or a dangling npm link could make a partial tree look installed to later resolution code. Nvm can then select a directory whose contents are incomplete, turning an installation failure into a later and less informative runtime failure. Validation keeps the transition from “prepared files” to “eligible installation” explicit.
Only after a successful installation does nvm move into post-install behavior. It can activate the version in the current shell, install npm when the selected release needs it, establish a default alias if one is not already present, read a default-packages file, and optionally reinstall global packages from another Node.js version. These actions change the user experience and surrounding state, but they are not part of the artifact’s identity. A default alias is a policy about what a future shell should prefer. Package migration is a reconstruction of global packages in a new version’s tree. Neither retroactively changes the binary or source archive that was acquired.
This separation is useful when a command appears to have “installed successfully” but the expected behavior does not follow. If the resolver selected the wrong version, inspect the request or metadata. If the archive was rejected, inspect cache and checksum conditions. If the build failed, inspect platform and toolchain prerequisites. If the tree failed validation, treat it as incomplete rather than forcing activation. If installation succeeded but node still resolves elsewhere, the issue belongs to shell activation and path state, not acquisition. The next concern is therefore not another download option but the environment transition that makes the installed tree active.