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
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
- Security best practices for Nix
- Best practices for organizational adoption
Best practices for using flakes
At Determinate Systems, we’ve made it abundantly clear that we believe that
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
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.
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
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
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:
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:
{ 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
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
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
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
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
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