background grid image
Image for post best-practices-for-nix-at-work
Mar 24, 2025 by Luc Perkins

Best practices for Nix at work

Last week, we at Determinate Systems held our first webinar, Best practices for Nix at work, presented by yours truly. The session was a lot of fun, there were some great questions, and we’re really excited to continue to do more.

It was so fun, in fact, that we’ll be doing another one next week, on Thursday, March 27th at 1 pm GMT-3, titled Up and running with FlakeHub Cache. It will cover some conceptual basics around binary caching in Nix, what sets FlakeHub Cache apart from other offerings, and how to best take advantage of FlakeHub Cache’s unique feature set. I hope to see you there!

In this post, I’d like to share some of the best practices that I covered in our initial webinar. As in the talk, I’ve split those into three sections:

Best practices for using flakes

At Determinate Systems, we’ve made it abundantly clear that we believe that flakes are the future of Nix. So I won’t cover why you should use flakes here, but I will lay out some best practices for how you should use flakes based on our deep experience as a company the last few years.

Use SemVer

On their own, flakes don’t have a concept of semantic versioning (SemVer). That’s because most current Nix users pull flakes, such as Nixpkgs, from source forges like GitHub. And since those source forges don’t themselves have any concept of semantic versioning, at least when it comes to serving tarballs, Nix doesn’t have the requisite information to work with. Other sources for flakes, like the local filesystem, also don’t have the appropriate “hooks” that Nix would need to support SemVer.

We at Determinate Systems have added SemVer to flakes with FlakeHub, and unsurprisingly we do recommend using it. SemVer in Nix can apply not just to CLI tools and standard packages, but also to things like running services. Why not publish version 1.4.0 of your authentication service or 0.2.5 of your main web application? You can even use Nix adoption as a reason to finally SemVer-ify your stack.

With SemVer, your Nix updates can become much smoother. Rather than updating a monolithic dependency like Nixpkgs every however-many months, you can make your updates much more granular, handling any issues with discontinuity in a much more gradual way.

And if a flake outputs many different versioned things? See more about that below.

Split things up into as many flakes as you need

In the past, the Nix community has generally gravitated toward a usage model that involves having few dependencies. We see this centralizing tendency in many corners and frequently encounter organizations that depend only on Nixpkgs in all of their projects.

But things don’t need to be so centralized. Flakes are a great mechanism for dividing Nix things up into smaller units, and we built FlakeHub to make flakes “cheap”—cattle, not pets, if you will. We built the flakehub-push Action to make this trivial. And don’t forget that you can put several flakes in a single repository, making this model friendly to large repos or even monorepos.

So how many flakes do you need? There’s no hard-and-fast rule here, but in general we recommend publishing one flake per versioned thing, where a “thing” could be, for example, a versioned package like a CLI tool or NixOS configurations for a service or a series of Home Manager configurations.

I might even venture to say that when in doubt, just create another flake. You can always consolidate into larger flakes later if need be.

Check out our upcoming webinar Up and running with FlakeHub Cache on Thursday, March 27th at 1 pm GMT-3

Sign up

Update your inputs often

Keeping your inputs fresh can help make your updates smoother. It can make the difference between fixing a series of small things incrementally over time and spending an afternoon trying to disentangle a mess where you’re not sure what is breaking what.

We have two tools that can help with this:

  • Our Flake Checker Action helps you keep your Nixpkgs inputs up to date and either warns you or straight-up fails your CI runs if Nixpkgs is more than 30 days old (or a different number of days if you wish).
  • Our update-flake-lock Action automatically updates your flake inputs on your preferred cron schedule and creates a pull request.

Avoid flake helper libraries if possible

While we do encourage splitting things up into flakes, we generally recommend not using flake helper libraries like flake-utils and flake-parts.

There is nothing intrinsically wrong with these libraries—they’re made by Nix veterans and they’re well built and maintained—but any time you introduce a flake input into your dependency tree, it’s then an input to anyone depending on your flake (and anyone depending on that, and so on). And so you should strive to eliminate unnecessary inputs when possible, and we think that these are generally not necessary.

Let’s start with flake-utils, which provides some helpers for system-specific flake outputs. A worthy goal, certainly, but a few lines of Nix code will get you the same thing. At Determinate Systems, for example, we have a few lines of boilerplate that we include in every flake to handle system-specific outputs. The lines look something like this:

flake.nix
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forEachSupportedSystem = nixpkgs.lib.genAttrs supportedSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
in { ... }

Then we can use that for things like package outputs:

flake.nix
{
packages = forEachSupportedSystem ({ pkgs }: {
client = ...;
server = ...;
});
}

flake-parts has more ambitious goals, striving to make flakes more module driven, but at the cost of, to my eyes at least, making flakes more verbose and convoluted. At Determinate Systems, we always prefer plain old Nix code or Nixpkgs lib functions and have always managed without abstractions like this.

If you truly need flake-utils or flake-parts, please don’t hesitate to use them. Just be aware of the cost that flake helpers like this incur and judge accordingly. If you find yourself relying on them in ways that can be readily replaced by a few lines of copy/pasted code, we say go with that.

Security best practices for Nix

One of the great promises of Nix is that it’s supposed to make building and deploying things more secure by eliminating some common sources of non-reproducibility. But Nix isn’t magic here and there are still some basic precautions you should follow.

Keep secrets out of the Nix store

Always keep in mind that the Nix store isn’t a typical filesystem for running ad-hoc operations like reading, writing, copying, and deleting files. Instead, it’s basically an instrument that Nix uses to function properly, and when you write something to the Nix store, it can be quite non-trivial to get it out.

And so you should engage in whatever practices necessary to prevent yourself from writing secrets to any Nix store—especially if that Nix store happens to be a cache like FlakeHub Cache.

One common practice in the Nix community is to use tools like agenix and sops-nix for managing secrets. These are really solid tools and we think that they’re fine for smaller organizations or deploying a small set of NixOS machines for personal use. But for larger organizations, we recommend going beyond this and avoiding static secrets entirely—or at least whenever possible. That could mean using Vault or an analogous service.

Follow the principle of least access

In larger organizations, we recommend a general policy of keeping your Nix things private and then granting access to them only when necessary.

For flakes, that means keeping your flakes private unless you absolutely need to distribute something to the Nix-using public. This ensures that only people inside your org can run operations like nix build or even nix flake show against those flakes.

For caching, this means avoid public caches like cache.nixos.org whenever possible and using trusted private caches like FlakeHub Cache instead. Public caches have lots and lots of good stuff in them, of course, but they’re massive and the security and review practices behind them are not always air tight. When you pull from them to build production systems, you’re working with a porous trust model. We built FlakeHub Cache as a strictly private cache with no public access whatsoever to fill this precise gap in traditional approaches to Nix caching.

Best practices for organizational adoption

Regardless of how wise the best practices mentioned above may be, they don’t mean much if you can’t get your organization up and running with Nix. While there is no one correct path to Nix adoption, we do have a few rules of thumb worth drawn from our experience working with organizations of many sizes.

Spread knowledge

While we and many others have been working hard to make Nix more straightforward to understand and use, it can be a challenge to get up to speed even with some conceptual basics like knowing what Nix even is and what benefits it can provide. The more knowledge about Nix that you can spread within your organization, the better.

We created Zero to Nix as a flake-first documentation source for beginners, and we do strongly recommend passing that along to your coworkers. There are also some great official resources, such as nix.dev.

Adopt a gradual approach

Bringing Nix into any organization can be disruptive in a variety of ways. But because Nix can transform so many things, there are lots of ways to gently expand usage.

Let’s say that you’re using Nix on just one team and only for development environments. Just one team can be enough to demonstrates gains—faster CI runs, more synchronized environments, fewer “works on my machine” mishaps—significant enough to compel other teams to take action.

So you can talk a second team into using Nix for development environments. And talk the first team into using Nix for packaging their tools and distributing them to other teams. And maybe talk a third team into converting their Dockerfile-driven services into NixOS services. Or you can convert all of the Python stuff in your org over to using Nix and then talk the PHP folks into doing the same. And so on.

Ideally, from our perspective, you would wake up one morning with your entire org safely and happily under the Nix umbrella, but we know that things don’t work that way. Fortunately, there are many paths to expanding internal adoption in the meantime.

See you soon?

This set of best practices isn’t super systematic but it does provide a taste of what we went over in the webinar and what kind of content you can expect in the future there. We hope to see you at the next one on FlakeHub Cache this week!

Check out our upcoming webinar Up and running with FlakeHub Cache on Thursday, March 27th at 1 pm GMT-3

Sign up

Share
Avatar for Luc Perkins
Written by Luc Perkins

Luc is a technical writer, software engineer, and Nix advocate who's always on the lookout for qualitatively better ways of building software. He originally hails from the Pacific Northwest but has recently taken to living abroad.

Would you like access to private flakes and FlakeHub Cache?

Sign up for FlakeHub