3.4k
Connect
  • GitHub
  • Mastodon
  • Twitter
  • Slack
  • Linkedin

Blog

It's Time to Bring Nix to Work

Ross Turk | 27 August 2025
It's Time to Bring Nix to Work

You've learned enough Nix to make it work for you. Maybe even more than enough. Maybe you’ve convinced your team to use it for a few projects, and everybody loves what it can do... but you're still not satisfied. You believe it could change the way your company does everything.

But not everyone on your team knows Nix. Sound familiar?

If you look through our Nix in the Wild stories, you will find that Nix champions within organizations often report that they exist in a silo. They are among a small number of people who know Nix, which makes them responsible for everything Nix. So when technologists from across the org come to them with Nix-related questions, they cannot say “RTFM” and expect them to figure it out on their own.

Unfortunately people are often not willing or able to learn Nix…even if it would make them vastly more productive.

Not Everyone Has the Time to Gnaw on Nix

There are a few reasons for this, but mostly it's because they're too busy to commit the time. They have jobs, too, and few of those jobs give them the push or the excuse to learn Nix. Not to mention they have lives and responsibilities outside of work! So learning Nix is just too much cognitive load for their working context—because, ultimately, there are far more of us who write and use software than build and package it.

This is exacerbated somewhat by Nix’s history and nature: it’s a decentralized project, built in many waves over a long period of time. Nix isn’t one thing, it's a whole thing—and this whole is much greater than the sum of its many moving parts. That makes it hard to know where to start. Do you dive in with Nix’s functional programming model, learning the Nix expression language and creating your own shell.nix environments? Do you embrace Nix’s begin-with-the-build philosophy, using it to build and package your own software? Do you start with flakes, which give you the ability to combine environments and builds in one workflow? Do you try to wrap your head around derivations, attribute sets, closures, and other Nix concepts?

Even the most intrepid user might think twice about digging into Nix after scanning its massive surface area.

This surface area distracts from all that’s great about Nix, to the point that sometimes it’s all newcomers notice. They just can’t look past it to see that Nix solves a wide range of wicked problems that arise at the intersection of people, processes, technology, and the environments in which they operate. These are problems that affect us all, but don’t always attract attention.

What problems are we talking about? What makes them so hard to solve?

To answer these questions, let’s briefly explore what Nix is, how it works—and why it’s radically different.

The Tao of Nix

Nix is three complementary things rolled into one. First, it’s a cross-platform, cross-language, cross-toolchain package manager. Second, it’s a virtual environment manager, offering the same huge breadth of platform, language, and toolchain support. Finally, it’s a declarative build system, complete with its own functional expression language. Nix builds are reproducible and deterministic by design: you declare the end state you want, Nix figures out how to build it.

Taken as a whole, Nix gives you a reproducible way to build, package, and distribute software runtime environments so you can reuse them across platforms and contexts: whether locally (while building and iterating as part of a team), in CI and testing, or in production; whether on-premises or in the cloud.

Nix builds all packages as input-addressed artifacts inside a read-only, top-level directory: /nix/store. Each package gets its own store path, and this path is always derived from its build inputs—i.e., the sum total of its build-time dependencies, including software and environment variables, plus its name and version. A package’s store path is immutable: once you build it, its contents do not change. Because the same combination of inputs always yields the same store path, it’s like a recipe for guaranteed reproducibility and determinism. This approach, called input addressability, likewise makes it possible to install multiple versions of the same packages—along with multiple, conflicting versions of the same dependencies—on the same system, at the same time.

Nix Has Superpowers

But what problems do these superpowers solve?

Dev environments break when shared across teams. Nix obliterates the chief culprit behind “Works on my machine” problems: missing or conflicting dependencies. Teams can use Nix to create and share dev environments that “travel with” their runtime dependencies—including those that conflict with installed system-wide binaries or libraries. Nix puts everything into unique paths under ./nix/store, so installing software never requires making global changes that wreck working setups on laptops or desktops.

Time travel across package versions is impossible. Not with Nix! It’s easy to install and run packages from different historical eras—on the same system, in the same runtime. Need glibc v2.32 (circa 2020) and kubectl v1.29.11 (2024) in the same environment? How about older versions of Python (e.g., v3.6.x) with obsolete dependencies like OpenSSL 1.0.2? No problem! Define your requirements in a flake or Nix expression that just works—anytime/anywhere. Nix makes it possible to time-travel across historical eras!

Containers or VMs are too isolated for local dev velocity. This isn’t a thing with Nix! So far as teams building or debugging locally are concerned, Nix environments are just isolated enough. They run as sub-shells on the local system, so—unlike containers—users still get access to their env vars, dotfiles, data, secrets, and other resources. In the subshell, Nix overrides environment variables (like $PATH and $LD_LIBRARY_PATH) so that the system resolves executables and libraries from the Nix store first.

Getting software to build and run everywhere is a very hard problem. Typically, Nix uses the same declarative build recipe to build packages for all supported platforms. This build recipe is reproducible, which means users can use Nix to build the package directly on their systems. This always just works. In the same way, the pre-built Nix packages users install always just work on Linux, BSD, and macOS, on both 32- and 64-bit ARM and Intel chips, taking advantage of hardware acceleration—like Nvidia, AMD, and Apple GPUs.

Runtime environments don’t work across the SDLC. With Nix the same environments that teams use to build locally also travel across the SDLC: DevOps engineers use them to build and test in CI and QA, while SREs use them to reproduce production issues in production-like environments. Organizations can export them as containers, run them in VMs, deploy them as Lambdas, etc. Nix puts an end to “Works on my machine” everywhere, in every phase of the SDLC.

You never know which versions of which software you’re running—or where. With Nix, provenance, determinism, and dependency bookkeeping are baked into the ground-truth of the build system itself.

This means SBOMs can be exactly generated from Nix’s build graph, eliminating ambiguity about which code gets into your systems … as well as how it got there. Nix’s declarative model not only protects against accidental substitutions in your own builds, but also (if an upstream dependency is compromised) gives you a precise map of where you're exposed and how to respond. Building with Nix enables a secure foundation for generating accurate SBOMs and implementing SLSA, Sigstore, and other improvements.

Flox is for Flocks

If Nix is so superpowerful, why doesn’t everybody use it?

Therein lies the rub. Nix gives you superpowers, yes, but it’s also perceived as super-hard-to-use. Even if Nix’s learning curve isn’t especially intimidating—once you take into account everything it does—the fact is that for most people, in most situations, learning enough Nix to use it securely and reliably at scale is a bridge too far.

That’s where Flox comes in. Flox was built to help all kinds of people access the power of Nix.

With Flox, users can build and share virtual environments using the 180,000+ software components already available in Nixpkgs. These environments just work across Linux and Mac, Intel and ARM—they even just work in WSL2!

Users don't need to have Nix installed before they install Flox, and the process is pain-free.

There is a Flox installer for macOS users and you can brew install flox. There are rpm/deb packages available for those on Linux and WSL2. When you install on Linux, Flox is registered as a yum/apt repository for seamless upgrades. You can also install Flox into an existing Nix or NixOS system.

Flox offers semantics that technologists are already familiar with: init, search, install, activate, push, pull, build, and publish. Users don't have to learn a new language, and if they won't have to dedicate any headspace towards the building & packaging that occurs behind the scenes. They simply flox install the software they need.

% flox install nodejs mkcert imagemagick openssh  # easy and familiar

When it comes time, they flox activate their environments and get a subshell. This is very similar to the one Nix provides with nix develop, but with Flox users are able to get there using basic building blocks that feel a lot like apt, yum, and brew.

Flox makes it easy to build repeatable environments, with very little learning required.

Can you do everything with Flox that you can do with Nix? No, because it’s designed to support a more focused set of workflows. But Flox’s easy-to-use abstractions let you do a surprising amount of what’s possible in Nix directly within Flox itself. Plus Flox doesn’t wall you off from Nix, so whenever you need direct access to its superpowers, you can still get them. Flox users can even define complex overrides, substituters, and write their own Nix expressions—even building and packaging their own software!

As Little or as Much Nix as You Need

Speaking of which: what happens when your users can't find what they need in Nixpkgs?

This is where your Nix expertise comes in handy!

Let's say you want to use a less well-known Python package that isn't in Nixpkgs, or use a Rust toolchain targeting WebAssembly or an embedded device. Or perhaps you have some internal software that is a bit tricky to install? Maybe you just have some helper scripts you want to inject into your users' environments.

Flox gives you a couple of powerful options.

  • First, you can build and package your own software—optionally inside a sandboxed Nix build environment—and then publish it to your own private catalog of packages. Once you publish your packages, you can install them anytime (and anywhere) you need them. And with Flox for Teams, you can build, package, and share software across an entire team, as well as run it in CI, or deploy it to production.

  • Second, users can install flakes alongside packages, using the same simple Flox commands. That makes it possible for you to craft flakes for specific situations and deliver them to users. All they need to do is flox install them or define them declaratively in their manifests.

Let’s unpack and explore what these do and how they work.

Building and Packaging Software with Flox

Flox makes it easy to build and package your own (or someone else’s) software. When you build with Flox, you can reuse your existing scripts, tools, and workflows and leverage the full power of Nix as a build tool.

Here’s how this works:

  • Manifest builds. Reuse existing build logic by defining declarative build recipes in your Flox manifest. Just define a [build] section and author simple bash logic telling Flox how to run it. Under its hood, Flox leverages Nix as a build tool; you can even build inside a Nix sandbox by defining this in your build recipe.

  • Nix expression builds. A Nix expression build is always a pure function of its declared inputs: source files, dependencies, compiler flags, environment variables, etc. Nix hashes all these inputs to create a unique store path for the result. This hash is a recipe for both reproducibility and determinism: the same inputs always yield the same hash. And given the same hash, Nix produces—or reuses—the exact same artifact.

With Manifest builds, you can reuse your existing build logic, usually with minimal changes; with Nix expression builds, you can reuse—or repurpose—existing Nix expressions, or create your own from scratch. Either way, you can build and package software that isn’t available in nixpkgs (your own or someone else’s) easily. Once you’ve done that, you can publish whatever you build to your private Flox catalog of packages:

$ flox publish vibe-coded-prototype

… as well as search for and discover it:

$ flox search vibe-coded-prototype
floxuser/vibe-coded-prototype Prototype of super-disruptive vibe-coded technology for vibe coding

… and install it anywhere. If you have multiple versions of the same package, you can search among and install one of these too. Here’s the output of running flox show vibe-coded-prototype

floxuser/vibe-coded-prototype – Prototype of super-disruptive vibe-coded technology for vibe coding
    floxuser/[email protected]
    floxuser/[email protected]

Installing one of these is as simple as running: flox install floxuser/[email protected].

Flakes Make Flox Even More Flexible

You can also author version-locked flakes so users can install software using familiar Flox commands:

% flox install github:flox-examples/claude-cli

From the user’s perspective, installing a flake works just like installing any other package. Under the covers, Flox uses Nix to evaluate the flake and build its outputs: packages, devshells, and other artifacts.

This is great for testing software before users publish it to a team’s shared catalog, or to their own private catalogs. It’s also great for reusing flakes you already have! Say you’ve pinned a custom devshell in a flake—with a specific Python version, linters, and dependencies. With Flox, you can just hand that flake to your team and tell them to flox install github:<your-username>/devshell-flake to get the same devshell.

This same pattern applies outside of dev environments too. Imagine you want to use both Nix and Kubenix to manage your Kubernetes (K8s) manifests—everything from Deployments and Services to ConfigMaps (and even CRDs). With Kubenix, you can define and pin these manifests as Nix expressions, locking them to specific sources and versions. Wrap those definitions in a flake and expose the manifests as an output, and your SREs can flox install github:<your-team-name>/k8snix to get the same bundle you tested.

This pattern is brilliant! You can use flakes to pin your Kubernetes manifests (for Deployments, Services, ConfigMaps, CRDs, or even a full operator stack) and, with Flox, hand them to the SRE team as a single flox install command they can run on a local KIND cluster for fast testing. For staging or production, SREs run flox containerize to export the flake as a container and deploy it through the usual pipeline.

Your SREs don’t need to think about Nix at all; instead, they use Flox to pull in the same reproducible runtimes your org uses in CI—whether that’s a devShell, a manifest set, or an operator image.

And if users are building their own environments, they can install software just like with pip or npm—or define it declaratively, in their Flox manifest.

Flox’s approach to declarativity is another feature that makes it a bit easier to use than Nix. So let's look at that.

Flox Manifests Are Easy

Flox environments are defined in a declarative manifest. When users create a new environment using imperative init and install commands, a TOML file is being assembled behind the scenes. You can use this file to edit, version, and share the environment. Flox creates a lock file for pinning package versions, so you can reliably reproduce the environment anywhere—at anytime.

We chose TOML for its readability. Even basic LLMs can read and understand Flox environment manifests.

This TOML file is packed with comments by default, but users probably won't need them. It contains sections for installing software ([install]), setting variables ([vars]), initialization hooks ([hook]), user shell configuration ([profile]), service definition ([services]), and, of course, building software ([build]. But most sections are optional, and a minimal manifest can be as small as:

version = 1
[install]
openssh.pkg-path = "openssh"

Users who need a bit more than a “virtual cross-platform package manager” (which is already awesome tbh) will find themselves running flox edit to tinker with the manifest pretty quickly. This will show them how to create environments that do stuff, like starting services, running npm ci, or downloading models from HuggingFace.

When it comes time to help users troubleshoot issues, this manifest gives you a way to exactly recreate their environment. You will be able to copy their manifest, spin up a matching environment, and troubleshoot with a vastly reduced set of variables.

Flox can help reduce the whole "it works on my machine" issue, even in heterogeneous environments.

Better still, your users will be able to create repeatable and portable environments that give them a glimpse of what Nix can do!

Authoring Environments for Users

You don't have to wait for your users to create their own Flox environments. You can create and share them yourself.

As someone who understands how Nix works, your Flox environments can do fancy stuff.

You know how to select the correct set of packages (i.e., choosing bashInteractive instead of bash), you understand how they behave when you combine them (i.e., what happens if you install both python310 and python311), you are comfortable working with all of the pythonPackages and nodePackages collections, and you know how to write Nix expressions or flakes that fill in everything that's missing.

For example, you could:

When you create your environments, you can share them with users by pushing them to FloxHub with flox push or checking the .flox directory into your repo.

At Flox, We Love Nix

We don't just depend on Nix as "our upstream," we rely on the magic of Nix with each and every build.

We want to bring this magic to as many people as possible. When you use Flox, you’re exposed to core Nix concepts like reproducibility, determinism, and atomic upgrades/rollbacks. And you also get a sense of how Nix works, with the Flox CLI abstracting just enough to make Nix a bit more intuitive and easier to use.

Flox is free to download and use. It's the best way to bring Nix to your teams where they work, using their preferred workflows. Give it a try, and recommend it to those who need it. Your users will thank you!

This lets teams build for multiple architectures without duct-taping together separate pipelines. They can create a single Nix expression that defines the build, and cross-compilation or native builds just work because dependencies are pinned and fully declared, not inherited from the host system’s state.