Blog
Using Flox to Create Portable, Reproducible Node.js Environments
Steve Swoyer | 07 October 2024
Flox makes it easy to create portable, reproducible Node.js environments that run the same way on Linux, macOS, and Windows (with WSL2), across both ARM and x86 architectures.
If you've got an existing Node.js project with a package.json
or a yarn.lock
file, it's just as easy to use it with Flox. You won’t even have to do much lifting, because Flox automatically installs Node.js for you!
This article provides a quick-start overview of how to create Flox environments for new Node.js projects, and shows how Flox integrates with existing Node.js projects, too. It also explores the benefits of using Flox to create, share, and manage your Node.js build environments. Beyond portability and reproducibility, Flox supports all languages and toolchains, which makes Flox dev environments ideal for Node.js projects involving WebAssembly (WASM), Python, Go, Rust, and other languages. And unlike containers, Flox dev environments give you transparent access to local resources, along with strict deterministic safeguards.
Ready to dig in?
How to Kickstart a New Node.js Project with Flox
So you're kicking off a new Node.js back-end project and you want to use Flox.
Smart choice! You can feel confident the Flox Node.js environments you create on your Linux system will work on the macOS and Windows (with WSL2) systems your teammates use to build locally, along with the VMs and containers your organization runs in CI and production.
Getting started with Flox is super simple. First, you just need to create and/or cd into your project directory.
Next, you initialize your Flox environment:
Think of each Flox environment as a self-contained, clean-room ecosystem, ready to be populated with packages, or configured with environment variables, setup and teardown logic, automated actions, and functions. It's like a container, only better, because it runs on your local system, so you enjoy transparent access to all of your files, settings, and resources.
To start things off, we need Node.js—but not just any version! Our project requires a version that’s greater than or equal to v20.0. Does the Flox Catalog have a compatible version of Node.js?
Yep. Sure looks like it does. Let’s use the flox show
subcommand to drill down into the nodejs_20
package:
Plenty of options. We'll go with the nodejs_20
metapackage and then use the flox list
subcommand to see which version Flox installs for us.
That'll do nicely. Under its hood, Flox is a genius at resolving package dependencies. It automatically figures out which versions of which packages can coexist in the same environment. And if there’s a conflict—like when two or more packages need the same version of the same library, which they expect to be installed to the same path—Flox gives you simple, declarative workarounds.
What’s next? We already get npm
with Flox’s nodejs_20
package, but what if we prefer to use another package manager, like Yarn. Does Flox have that, too?
Nice! Flox has both Yarn Classic and Berry, the name given to Yarn versions 2.0 and greater. I can use Flox to install either. But what if I want to use a different package manager—say, pnpM? Does Flox have that as well?
Why yes—yes it does. Now, let’s say for the sake of argument that we're JavaScript-runtime curious and we also want to install Bun, a hip new alternative to Node.js. Can I get this from Flox too? You bet!
This section is already long enough, so we won’t show the 347 other results surfaced by flox search
. You get the point. The Flox Catalog has much of what you’ll need—including commonly used packages like electron, pm2, parcel, node-gyp, and hundreds of others. Or useful add-ons like create-react-app, mocha, or even eslint_d, and more.
Let’s say I install all of these into my environment. Here’s what this looks like in our software manifest, manifest.toml, Flox’s declarative environment configuration artifact. This file lives in the .flox
subdirectory that's nested inside every Flox environment. Instead of installing packages imperatively, as with flox install nodejs
, we can declaratively define them under [install]
, even specifying versions:
By adding the line nodejs_20.version = "nodejs-20.12.2
, we can explicitly define and pin a specific version of Node.js. And we can do this with any package, e.g. first using flox show <package_name>
to figure out which package versions are available in the Flox Catalog. So if we want to use a specific version of the create-react-app
, for instance, we can just declaratively define it under [install]
:
Neat, right?
As a final step, let's fire up our environment by typing flox activate
:
Now let’s use one of our installed packages (pm2
) to run a “Hello, World!” example. Offscreen, I’ve created an index.js
with logic that prints this message to the console. Let’s see if my JavaScript one-liner works.
Success!
You can use Flox to install core Node.js packages, like the ones above, as well as configure environment variables, settings, and overrides for your project environments. From there, just use your preferred package manager to install project-specific packages.
Flox environments are especially useful when you’re working on polyglot project architectures involving multiple programming languages and toolchains. You can get versions of WASM, Python, Go, Rust, C++, and other required dependencies from Flox Catalog, installing software packages optimized both for operating system platforms (Linux and macOS) and chip architectures (x86-64 and ARM). The upshot is that Node.js projects you create on your M1 MacBook will run with native binaries and libraries in x86-64 containers in production!
How Flox Integrates with a Pre-Existing Node.js Project
But what if you want to use Flox with an existing Node.js project—like a project in a GitHub repo that defines its package dependencies using a package.json
or yarn.lock
file? No problem! Whether you're just starting from scratch or you need to integrate with an existing project, Flox is the first-class solution for creating portable, reproducible, declarative Node.js dev environments that Just Work … anywhere.
Let’s use the official Kubernetes (K8s) documentation website’s GitHub repo as an example project.
You can clone this project from Github and either spin up a local instance, powered by Hugo, or build the whole thing into a container and run it that way. For what it’s worth, the K8s project “strongly recommends” using the latter approach, which it says is more reliably reproducible, but we’ve got Flox, whose superpower is reproducibility ... so why not build locally?
Next, let’s initialize a new Flox environment:
What, exactly, happened here? We didn’t see this behavior when we bootstrapped a fresh Node.js project by running flox init
in the first section. This time around, Flox automatically detects the package.json
in the project directory, so it asks me if I want it to install version 20.17.0 of Node.js.
Let’s see what other mods it wants to make. By selecting “Show suggested modifications,” we can preview the entries Flox will create for us in this environment’s manifest.toml
.
The [install]
section defines the version of Node.js the Kubernetes documentation website needs to run. The [hook]
section lets you specify activation hooks and other automations for an environment. Here npm install
automatically installs the packages (if any) specified in package.json
.
Let’s go ahead and accept these modifications:
Next up, the K8s documentation repo showcases Flox’s built-in automations for Go. Detecting a go.mod
file in the project directory kickstarts a bootstrapping wizard that installs the required go
package:
Let’s again see what changes Flox wants to make:
Nothing unusual here. Let’s accept the modifications and keep going.
To build and serve the Kubernetes documentation website locally, we’ve got to install required dependencies, like the hugo
package. The packages in the Flox Catalog are usually self-describing, so let’s try installing them by name, rather than first flox search
-ing for them.
We already have npm
and go
; the only thing missing is hugo
:
Before we activate our environment, let’s just see what the [install]
section of our software manifest looks like:
Looks good. Now let’s go ahead and activate our Floxified K8s documentation environment!
Before firing up our local instance of the K8s documentation website, the install instructions prescribe some prep work, which we’ll do off-screen. This includes running make module-init
so required submodules (like the Docsy Hugo
theme) are initialized and available, and running npm ci
(rather than npm install
).
This done, we can run make serve
to trigger the makefile target that (a) builds the website with Hugo and (b) starts the local dev server. Here’s what this looks like running inside a Flox environment:
It seems like our local instance of the K8s documentation website is up and running. But let’s open it up in a web browser to make sure.
Success! All told, Floxifying a repo with a package.json
file took less than two minutes!
Flox Takes Node.js Dev to the Next Level
Flox makes it trivially easy to create and share portable, reproducible Node.js development environments.
Need to compile Rust libraries to WASM for a Node.js app that transforms and encrypts data? No problem! Just add rustc
, cargo
, clippy
, wasm-pack, wasmtime, or any required dependencies to your Flox environment’s software manifest. You can feel confident your Flox Node.js environments will run and behave the same across all phases of your SDLC—from local development on macOS, Linux, or Windows (with WSL2), to the Linux VM and container runners you use in CI and prod.
It's simple to get started using Flox. In fewer than five minutes, you can set up a Node.js dev environment on your local machine, with complete access to all your tools, files, and resources. To share your environment, just run git push
or flox push
(to push to FloxHub).
That's all it takes! Your teammates can git clone
or flox pull
your environment to run it locally.
Download Flox today and discover how it can transform your Node.js workflow!