Library / SDK
ekalinin/nodeenv avatar
ekalinin/nodeenv

nodeenv: isolated Node.js environments with a pinned runtime

Virtual environment for Node.js & integrator with virtualenv

1,792 stars225 forksPythonNOASSERTION

At a glance

What is it?
nodeenv builds a self contained Node.js installation in a directory, with its own npm packages, and can pin the Node version, mirror the download and reproduce the package list from a requirements file. It suits Python projects that also run a JavaScript build, and it needs make and tail on the machine that builds the environment.
Who is it for?
Adopt nodeenv if you need a Node runtime that belongs to one project rather than to the machine, for example a CI job that must build with the same Node version as production, because the environment is a directory you can commit alongside a requirements file and rebuild anywhere. Do not adopt it as a version switcher for interactive shell work, since it is built around per-project isolation rather than swapping the active interpreter.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What nodeenv creates and why it is not a version manager

nodeenv is a tool to create isolated Node.js environments. It creates an environment that has its own installation directories and that does not share libraries with other Node.js virtual environments.

That is the whole design, and it is worth being clear that it is not a version manager. A version manager changes which Node binary is active in your shell. nodeenv puts a complete Node installation inside a directory, next to your project, and leaves the rest of the machine alone. Two projects can therefore run different Node versions, and different sets of global npm packages, at the same time, with nothing shared between them.

The second property is integration with Python. The new environment can be integrated with the environment built by virtualenv, and the project asks users to add theirs to a wiki page listing who uses nodeenv. The Makefile in the repository shows the two test targets that make this explicit, one named test1 with the comment about a separate nodejs env, and test2 for the same virtualenv's env.

The project is a single module. The setup script declares py_modules as nodeenv, exposes a console script called nodeenv, and has an empty install_requires, so the tool itself pulls in nothing at runtime. The version is read out of the module source rather than hardcoded in the metadata, and the release process in the Makefile greps the version out of the source file to tag the commit.

The licence is BSD, and the last push to the repository was on 2026-10-15.

Creating and activating the environment

The basic flow is three commands, and the names of the scripts follow the virtualenv convention so that the muscle memory transfers.

bash
$ nodeenv env
$ . env/bin/activate
$ node -v

The activation script puts the environment's binaries on the path, and the environment reports its own versions afterwards, in the example a Node of v0.10.26 and an npm of 1.4.3.

Windows is handled explicitly, because there is no single shell there. The environment is created in env\Scripts instead of env/bin, with a script per shell, activate.bat for cmd, Activate.ps1 for PowerShell and activate for posix shells such as git-bash.

Deactivation has its own name, deactivate_node, rather than the plain deactivate that virtualenv uses. That is a small detail with a practical purpose: you can be inside a Python virtualenv and a nodeenv at the same time, and two different commands to leave them means you do not accidentally leave both.

The README also shows the local installation path, which is the one that makes sense inside a Python project. You create a virtualenv, activate it, install nodeenv into it with pip, and then nodeenv is available only inside that Python environment.

bash
$ virtualenv env
$ . env/bin/activate
(env) $ pip install nodeenv
(env) $ nodeenv --version

Choosing the Node version, including ranges and .node-version

The version selection is the part of the tool with the most options, and it is where nodeenv has grown well beyond the original use case.

The simplest form is a fixed version, with --node set to a release and an environment name. On top of that, nodeenv accepts npm semver syntax, so --node=22, --node=4.x, --node="^4.3.1" and --node=">=20 <22" all work, and the same syntax works in a.node-version file, which is the convention the Node ecosystem already uses for version pinning.

bash
$ nodeenv --node=22 env-22
$ nodeenv --node=4.x env-4
$ nodeenv --node="^4.3.1" env-4.3
$ nodeenv --node=">=20 <22" env-20

Two more modes are about not installing anything. --node=system creates an environment that uses the system-wide Node.js, and --prefer-system with --node=lts uses the system Node if it is available and otherwise installs the latest LTS release.

The --list option prints the available versions, and the list in the README runs from 0.0.1 through the early releases, which is a reminder that nodeenv has been able to install Node from the very beginning of the project.

The npm version can be pinned separately with --npm, and there is a --without-ssl option for the old releases that were built without SSL support, which is how an old release could be installed on a current system.

Prebuilt versus source, and mirrors

The performance difference between downloading a prebuilt Node and compiling it is the difference between a short wait and a several-minute build, and the README measures it.

bash
$ time nodeenv --node=0.10.25 --prebuilt env-0.10.25-prebuilt
 real    0m6.928s

$ time nodeenv --node=0.10.25 --source env-0.10.25-src
 real    4m12.602s

Seven seconds against four minutes and twelve seconds. The README notes that it is much faster to install from the prebuilt package than to install and compile Node from source, and the --source flag exists for the cases where you need it, such as a platform with no prebuilt binary.

The mirror options are the other half of the network story, and they are unusually complete. A mirror is a URL that repeats the layout of nodejs.org, with packages under v<version>/ directories and an index.json next to them so that latest, lts and version ranges can be resolved.

bash
$ nodeenv --node=10.19.0 --mirror=https://npm.taobao.org/mirrors/node
$ nodeenv --node=22.14.0 --mirror=file:///srv/node-mirror env-22

A local directory works as a mirror too, which is how an air gapped build or a company mirror works. And a mirror that asks for a login takes the credentials in the URL, with special characters percent encoded, which is the case for an authenticated Artifactory or Nexus instance.

The --jobs flag controls the number of parallel compile commands, which is the setting that matters when you do build from source.

freeze: capturing the package list without pinning the runtime

Once an environment has packages installed globally, nodeenv can record them. The freeze command saves the versions of all installed packages to a file.

bash
(env-4.3)$ npm install -g express
(env-4.3)$ npm install -g jade
(env-4.3)$ freeze ../prod-requirements.txt

The file is a plain text list, one package per line with the version after an at sign, and the README gives the example.

The reason this is a Python tool's command rather than an npm one is that the format matches a requirements file, so the same convention, the same line format and the same tooling apply to the JavaScript dependencies of a Python project.

There is a deliberate omission. npm and corepack are installed by Node itself and are left out of the list, because pinning them would downgrade the copies that the next Node brings. The runtime is pinned separately, by writing the version next to the requirements file, since nodeenv reads .node-version from the directory it is run in.

bash
(env-4.3)$ node --version > ../.node-version

So the environment is described by two files, one for the runtime and one for the packages, and both are checked into the project.

The -l option lists locally installed packages instead, which is the counterpart for modules that live in node_modules rather than in the global prefix.

Requirements files, and local versus global packages

A requirements file can be used to create an environment directly, which is the reproducible build path.

bash
$ nodeenv --requirements=../prod-requirements.txt --jobs=4 env-copy

The flag may be given more than once, and the packages of every file are installed globally.

bash
$ nodeenv --requirements=../prod-requirements.txt \
          --requirements=../dev-requirements.txt env-copy

That gives you a development environment and a production environment from the same runtime, which is the arrangement most Python shops already have for pip.

Local packages are handled by --local-requirements, which accepts the output of freeze -l and may also be given more than once. Global packages are installed first, then the local ones.

bash
$ nodeenv --requirements=../global-requirements.txt \
          --local-requirements=../local-requirements.txt env-copy

The local ones land in node_modules of the current directory, which is the Node convention for project dependencies, so a project can have both a global toolchain and a local dependency tree.

The README states the purpose of the whole mechanism in one line, that these text files allow you to create repeatable installations. That is the argument for the tool: not that environments are isolated, since a directory is easy to isolate, but that the environment can be rebuilt from two small text files on a different machine.

The limits, the dependencies and the state of the project

Three practical constraints.

The first is the dependency list. For nodeenv itself the requirements are Python, either 2.6 or later, 3.5 or later, or pypy, plus make and tail. For Node.js itself, libssl-dev is needed. The presence of tail and make is the kind of detail that breaks a minimal container image, and the dependencies section is right at the top of the README, so it is discoverable.

The second is the Python floor in the package metadata, which allows 2.7 and everything from 3.7 upwards, excluding 3.0 to 3.6. The classifiers list 3.7 to 3.10, and the release history shows the project adding Python 3.13 support in the 1.10.0 release in December 2025, so the metadata is conservative in places and current in others.

The third is that this is a downloader and an unpacker, not a builder. nodeenv fetches Node and lays it out, and it can compile it from source with --source, but the point of the tool is the environment, and the environment is a directory. If your requirement is a reproducible Node toolchain with a lockfile, that is a different tool, and this one is not trying to be it.

On maintenance, the releases are 1.11.0 on 2026-09-26, 1.10.0 in December 2025 and 1.9.1 in June 2024, the last push was on 2026-10-15, and the licence recorded in the repository metadata could not be classified by GitHub even though the setup script declares BSD, so the LICENSE file in the repository is the reference.

Editorial conclusion

Adopt nodeenv if you need a Node runtime that belongs to one project rather than to the machine, for example a CI job that must build with the same Node version as production, because the environment is a directory you can commit alongside a requirements file and rebuild anywhere. Do not adopt it as a version switcher for interactive shell work, since it is built around per-project isolation rather than swapping the active interpreter. Before you build an environment on a build server, check that make and tail are installed, because nodeenv lists them as its own requirements and the installation fails without them.

Frequently asked questions

What does nodeenv create?

nodeenv creates an isolated Node.js environment with its own installation directories that does not share libraries with other Node.js virtual environments. It can be installed globally, or inside a Python virtualenv so the two environments integrate.

How do I pin the Node version in nodeenv?

Use the --node flag, which accepts an exact version or npm semver syntax such as 22, 4.x, ^4.3.1 or >=20 <22. The same syntax works in a .node-version file, which nodeenv reads from the directory it is run in.

What does the freeze command in nodeenv do?

freeze saves the versions of all globally installed packages to a text file, one package per line with the version after an at sign, in the same format as a requirements file. The -l option lists locally installed packages instead, and npm and corepack are deliberately left out of the list.

What does nodeenv need installed on the machine?

The README lists Python, either 2.6 or later, 3.5 or later, or pypy, along with make and tail for nodeenv itself, and libssl-dev for Node.js. A minimal container image that lacks make or tail will fail during the environment build.

Official sources

  1. ekalinin/nodeenv on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ekalinin-nodeenv.svg)](https://hysenlabs.com/projects/ekalinin-nodeenv)

Was this useful?