background grid image
Image for post nix-flakes-explained
Nov 19, 2025 by Jeff Martens

Nix flakes explained: what they solve, why they matter, and the future

Hello! I’m Jeff and I’m basically the “go-to-market guy” here at Determinate Systems. I’m reasonably “technical,” as they say nowadays, but I am not an engineer let alone a Nix engineer.

Understanding Nix has been one of the bigger challenges in my 18 years working with developer tools. At times I’ve wanted to put my focus elsewhere, but the thing is, I simply can’t afford to interact with our customers and with the community without knowing my stuff. Do I need to know how to deploy NixOS systems to production? Fortunately not! But I do need to discuss some basics of Nix intelligently to be effective in my role.

While I’ve learned lots about Nix this past year, I want to focus in on how I’ve come to understand Nix flakes because they’re central to our mission here at Determinate Systems. Until recently, flakes were pretty mysterious to me. But my colleague Luc Perkins’ recent webinar, Nix flakes in practice: what they solve and why they matter, really helped me get over the hump. I’ll spend the rest of this post summarizing what I took away from it, with the hope that it might resonate with others in a similar position.

The flakes backstory

Before flakes, Nix had a reputation for being powerful but also unwieldy. Developers often struggled with unpinned dependencies, inconsistent conventions, and workflows that weren’t truly reproducible. Flakes were introduced to fix these problems, and while they don’t solve everything, I do think that they represent a major leap forward for Nix.

In this post, based on our recent webinar, I’ll explore:

  • What Nix flakes are and how they work.
  • The problems flakes solve (and don’t solve).
  • Why flakes matter for reproducible builds.
  • How Determinate Systems is extending flakes with FlakeHub and Determinate Nix.
  • Where flakes are headed next.

The problem with Nix before flakes

Before flakes, importing dependencies in Nix often looked like this:

{ pkgs ? with import <nixpkgs> }:
{ ... }

Simple but also sadly indeterminate. Two developers could run this on different machines and get completely different results.

Pinning was possible using tools like fetchTarball, niv, or npins. But the lack of a standard approach meant:

  • Multiple competing conventions.
  • No guarantee of reproducibility in your dependencies.
  • A slow accumulation of what Luc in the webinar calls indeterminacy debt over time.

On top of this, Nix workflows were tied to file naming conventions—the nix-build command tied to default.nix, the nix-shell command tied to shell.nix, and so on—with poor discoverability compared to a lot of modern developer tooling.

For many developers, the promise of reproducibility was undercut by the reality of inconsistent inputs.

What are Nix flakes?

A Nix flake is a directory with a flake.nix file at its root. Generally speaking1, that Nix file defines two things:

  • Inputs are other flakes or sources that your Nix project depends on.
  • Outputs are packages, development environments, NixOS configurations, or other kinds of artifacts. You use the flake inputs to build those outputs.

Conceptually, you think of flakes as behaving like functions:

graph LR
  I[inputs] --> M((+))
  S[self] --> M
  M --> O[outputs]

  style I fill:#faa61a,stroke:#faa61a,stroke-width:2px,color:#fff
  style S fill:#cd1e8b,stroke:#cd1e8b,stroke-width:2px,color:#fff
  style M fill:#b3b3b3,stroke:#b3b3b3,stroke-width:2px,color:#000
  style O fill:#0d4d9c,stroke:#0d4d9c,stroke-width:2px,color:#fff

Every flake also comes with a flake.lock file that records input revisions. That means that the entire dependency graph becomes deterministic, while running nix flake update makes version changes explicit and controlled.

This makes Nix reproducible in a way that you can’t opt out of even if you want to.

What Nix flakes solve

Flakes close the loop on what Luc in the webinar calls the distributed input problem. Instead of each project and team pinning dependencies in ad-hoc ways, flakes provide a single, unified mechanism for determinism.

Here’s what that means in practice:

  • Deterministic dependencies. Every dependency is pinned in the flake.lock file. Whether your project pulls from a local path (./server), the Nix registry (nixpkgs), GitHub (github:nix-community/nixos-generators), or FlakeHub (https://flakehub.com/...), the exact versions are locked. Two developers on opposite sides of the world can run the same command and get the same results.

  • Reduced indeterminacy debt. Without flakes, uncertainty accumulates. Each unpinned input introduces a little risk and, over time, those risks compound. Flakes prevent that debt from building in the first place.

  • Portable and shareable environments. A flake reference essentially represents a complete Nix dependency graph in a single string. That makes it trivial to move between contexts: local development, CI pipelines, deployment targets, or even teaching examples.

  • Better tooling and discoverability. Flakes integrate with new commands like nix flake show (to inspect outputs) and nix search (to find packages). These small quality-of-life improvements address one of Nix’s long-standing pain points: discoverability.

Together, these features make flakes more than just a new syntax. They transform Nix from a collection of clever but scattered practices into a cohesive, reproducible workflow that teams can rely on.

What flakes don’t solve

Flakes represent real progress, but they aren’t a silver bullet. They solve the distributed input problem I mentioned above because they make the entire input graph deterministic. But they don’t solve what Luc calls the output problem. Flakes don’t and can’t fix things like broken derivations or misconfigured NixOS modules, nor do they prevent non-determinism from creeping into your Nix builds. With flakes, you can still do things like this:

  • A derivation that writes date > time.txt produces different results on every build.
  • Builds that rely on network access, system time, or environment variables can’t be guaranteed reproducible.
  • Malformed derivations or ambiguous output structures are still possible.

Flakes don’t protect you against these pitfalls. What they do instead is they make it easier to spot, isolate, and fix those problems because the input graph is now predictable. In other words: flakes give you a stable floor to stand on, but the integrity of the walls you build is still up to you.

Challenges with flakes

Even with deterministic inputs, flakes introduce their own challenges. Chief among them is file copying.

When you fetch inputs in Nix, you often end up duplicating a lot of data. Flakes improve this somewhat with lazy input fetching, meaning that inputs are only retrieved when needed. But the more fundamental problem is what Luc calls the correctness tax: every optimization around file handling carries trade-offs.

This is where Determinate Nix enters the picture. It introduces lazy trees, a feature that makes file copying dramatically smarter by copying only what’s necessary. This reduces overhead and improves performance, without compromising correctness.

This distinction matters. Flakes make dependency graphs deterministic, but they don’t address performance or scalability on their own. That’s where tools like Determinate Nix add real-world practicality.

The future of Nix flakes (and how Determinate Systems fits in)

Flakes have unlocked a new era for Nix, but the ecosystem is far from finished. At Determinate Systems, we see two parallel tracks of progress: extending flakes with practical tooling today, and helping shape their evolution for tomorrow.

Making flakes work today

We’ve invested in tools that make flakes stable and usable in production:

  • Determinate Nix adds:

    • Stable flakes (ensuring reliability in fast-moving environments).
    • Lazy Trees (smarter file handling, exclusive to Determinate Nix).
    • Parallel evaluation (faster builds on multi-core machines).
  • FlakeHub is our platform for:

    • Publishing and discovering flakes.
    • Using semantic versioning for dependency management.
    • Hosting private flakes for enterprise adoption.

Together, these make flakes not just a clever idea, but a practical foundation for modern DevOps and much else besides.

Evolving flakes for the future

Looking further ahead, several important directions are already taking shape:

  • Flake schemas: a way to define and validate new output structures beyond the current defaults. This unlocks support for things like Docker images or macOS (darwinConfigurations).
  • Sparse lockfiles: more efficient handling of large dependency graphs, reducing lockfile size and improving evaluation speed.
  • Configurable flakes: a step toward more flexible, reusable flakes that can adapt to different environments without duplicating logic.

These are not abstract wish list items: they’re active areas of development, and Determinate Systems is directly involved in pushing them forward.

Conclusion

I’m convinced that flakes represent the single biggest step forward in Nix since its inception. By making inputs deterministic, they provide the stable foundation that reproducibility demands. But they don’t eliminate every problem. Outputs can still be indeterminate and scaling flakes for real-world use cases may require further innovation.

That’s where Determinate Systems comes in. We’re building the tools, platforms, and standards that make flakes reliable today and more powerful tomorrow.

The future of Nix is flake shaped, and we’re just getting started. Thank you all for humoring me and go easy on me, I’m still learning 😇

Want to try flakes in practice? Explore our documentation or join the discussion on Discord.

Finally, I’d like to thank my talented and patient colleague, Luc Perkins, for helping me with this blog post and the technical details required to write about my learning journey with Nix and flakes.

Footnotes

  1. Some flakes, like Nixpkgs, have no inputs, only outputs. You can also provide a text description of the flake using the description attribute and specify Nix configuration using the nixConfig attribute.


Share
Avatar for Jeff Martens
Written by Jeff Martens

Jeff has been building developer tools for the last 15 years, in both product management and startup founder roles. He believes that the best way to deliver great experiences with software to end users is to empower developers with delightful tools&mdash;like Nix. At Determinate Systems, Jeff leads Go-to-Market, corporate strategy, and operations.