Imagine that you just started a new data project that requires Python 3.10, but you have Python 3.8 already installed globally on your machine. You cannot upgrade Python because another project depends on the older version. You download both versions and create a bunch of aliases and shims to get by.
Imagine you are a web developer. One project needs NodeJS 16, and another NodeJS 18. Now you need a version manager called NVM to manage your NodeJS(es).
Challenges with globally installed packages
Popular package managers such as Homebrew and apt-get install packages globally. These packages are mutable and shared across all of your projects. Switching versions as you jump between projects is already painful, but these packages are mutable, meaning changes that happen in one place are unintentionally inherited by another. Take PHP, which can statically compile extensions defined in one project into the global PHP binary. Running the same PHP binary in a different project could result in a wrong set of extensions being used.
Challenges with locally installed packages
On the other end of the spectrum, you have package managers such as NPM that install everything locally (e.g., in node_modules
). Creating a new project means installing everything from scratch again. This works well if the package dependency tree is small. A counter-example is NodeJS, which needs V8, libuv, llhttp, c-ares, OpenSSL, and zlib to run, and Python and C to build and compile. If you replicate all of them down to the lowest level, you would have a Nix store per project:
Nix creates an immutable global store to guarantee correctness
We know it is unrealistic to install everything locally, so things need to be shared globally in some way or another. We also know that changes to one project should not affect other projects: actions within a workspace should be isolated and self-contained. Nix guarantees correctness, reproducibility, and isolation by making all packages immutable under the Nix store. However, Nix does not make it easier to switch versions of a package, and packages like NGINX that expect a mutable configuration no longer work in a standard way.
Devbox: immutable global store with local mutable environment
We designed Devbox to tackle all of the above with a combination of global store and virtual environments. Devbox installs packages globally in the immutable Nix store and creates a local virtual environment based on your project devbox.json
config. Say you want Python 3.10 in the current directory. A simple devbox add python310
command installs Python 3.10 globally under /nix/store
. Running devbox shell
creates a local virtual environment that only makes Python 3.10 available while ignoring all other versions. Analogous to npm's package.json
, Python3.10 is declared and configured in a devbox.json
file, saved in a directory of your choice. Here’s an example of setting up python and pip in devbox.json
:
{
"packages": [
"python310",
"pipenv"
],
"shell": {
"init_hook": "pipenv shell",
"scripts": {
"test": ...
}
}
}
Devbox lets you develop on any machine
We took the convenience of virtual environments, the UX of npm, and the immutability of NixOS to create this magical local-like experience with Devbox. Your dev environments configured with devbox.json
are portable and easy to install and deploy on different machines. In an upcoming blog post, we'll show how Devbox can let you develop on any machine, even in the cloud.
Want more? Join our Discord community to get instant updates on blog posts and upcoming features. Head to GitHub and check out the source code. Visit jetify.com/devbox to learn more.