Dec 28, 2021

Using Nix as a Professional

This originally appeared on [Marty Henderson’s personal blog](https://nalth.is/using-nix-as-a-professional/)

Summary

There’s a chance that if you’re reading this, you’re aware of NixOS or Nix. If not, welcome to a quick summary.

Nix is both a tool and an outright operating system. Although I love it for a Linux operating system, I am going to assume you are using it as a package management tool. In its most pure sense, it allows you to define which software and what version you’re using, by leveraging declarative language. It’s agnostic to operating systems, languages, or tooling. I personally use it with my Mac so that I can have a consistent environment for each of my projects, which, if you’ve been reading my blog, you’d be aware travels across a lot of languages and tool.

With that quick summary out of the way, let’s talk about how to install it, use it, an excellent repository of simple configurations, and a dive into a living example.

Installing Nix

Installing Nix as a tool is extremely easy, thankfully.

They provide instructions for multiple operating systems, but on a Mac you simply run




sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume --daemon



Mac has an issue with encrypting volumes within volumes for the Nix store. Although it’s perfectly safe, you do have to install it in such a way to deal with encrypted volumes.

This installs Nix, pure and simple. You can test it by running

nix-env --version

If you get a response, you’re good to go

Nix Shells

Now that you have the tool, how do you use it?

Maybe not the easiest way, but the correct way is to explicitly declare precisely what you need in a given environment. To do this, you write a file called nix.shell with exactly what you need. Although I have several in personal projects, the master of keeping good nix shells is Seth Doty in his aptly named nix-shells repository. If you grab one of his files and put it in your directory, simply run nix-shell and Nix will grab the packages from the Nix store and build them. Let’s grab an example, his Go Nix Shell.

The file itself builds Go 1.14 and provides no additional shell hooks (meaning Go works as a command, but no other aliases)

with import <nixpkgs> { };

stdenv.mkDerivation {
  name = "go";
  buildInputs = [
    go_1_14
  ];
  shellHook = "";
}

The opening is consistent across all shell.nix files where it is importing packages. However, his mkDerivation flag means that it’ll grab the go package but tell it specifically to build 1.14 instead of whatever is the latest Go package. Pretty straight forward and simple, yeah? Let’s dig into one of his better ones, specifically, the Rust Nix Shell.

let
  moz_overlay = import (builtins.fetchTarball
    "https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz");
  nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
in with nixpkgs;
stdenv.mkDerivation {
  name = "moz_overlay_shell";
  buildInputs = [
    #nixpkgs.latest.rustChannels.nightly.rust 
    nixpkgs.latest.rustChannels.stable.rust
    rustfmt
    #rustup
    #cargo 
  ];
  shellHook = ''
    alias help="
        echo 'cargo new _'
        echo 'cargo build'
        echo 'cargo check'
        echo 'cargo run'
        echo 'cargo build --release'
    ";
    alias setup="
      rustup install stable
      rustup default stable
    ";
    cd $HOME/workspace/Rust;
  '';
}

This leverages the Firefox build overlay (because Mozilla created Rust, effectively, and started using it in Firefox, which builds in a ton of features and functions.) What’s more is that not only does it grab the Rust stable version, but it also sets up aliases based on the rustup toolchain. This means whether you grab this file, I grab this file, or Seth grabs this file, we’ll have the same aliases, the same latest Stable version, and the same workspace for Rust. It’s consistent across the board and Nix ensures that.

He has plenty more files, including Kubernetes which also plugs you into the demo dashboard, Docker and Docker-Compose and others. They are great foundational files as you build your own. As a professional, it’s more than acceptable to start with something working and expand it to what you need.

Once you have the shell.nix of your choice, simply run nix-shell and it’ll load your environment up specific to that shell. It’ll download, do the build, and load it up just the way you (or Seth) declared it.

So, what do you do next? Commit it into your repo! By having a shell.nix directly in your repo, you and your fellow contributors can git clone a project and immediately run nix shell to be using the exactly same builds for every member with a single command. It’s not just a fringe tool, either, professionals in many industries love it.

When do professionals use nix?

In repositories, so that developers are consistent! Some great examples are:

  • Waypoint – Hashicorp’s tool for automated deployment. (Side note, co-founder of Hashicorp, Mitchell Hashimoto, uses NixOS as his primary development environment and even tweeted about it and keeps his NixOS Configuration public on GitHub.)
  • AT&T – An excellent blog post for explaining Nix versus other solutions.
  • Red Hat – A great video on how and why they are using it.

If we dig into the Waypoint, the first thing you’ll notice is a reference to flake

(
  import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
    src = builtins.fetchGit ./.;
  }
).shellNix

What Flake does is it takes nix to the next level, by pinning the exact versions you are on to a flake.nix and a flake.lock. How it works is that flake pins to specific repo’s /nix/ directory and leverages a combination of *.nix files and most importantly an overlays.nix file. Without getting too deep, overlays allow you to apply explicit definitions for overriding packages with a concept of super arguments. This pins specific versions in Waypoint’s overlays.nix by referencing the other *.nix files.

As we can see, the overlays.nix file declares which files to import for version

final: prev: {
  # This is the pinned protoc version we have for this project.
  protobufPin = prev.protobuf3_15;

  devShell = final.callPackage ./waypoint.nix { };

  # Need to manually do this since 1.16 is still the default
  go = final.go_1_17;

  go-protobuf = prev.callPackage ./go-protobuf.nix { };

  go-protobuf-json = prev.callPackage ./go-protobuf-json.nix { };

  go-tools = prev.callPackage ./go-tools.nix { };

  go-mockery = prev.callPackage ./go-mockery.nix { };

  go-changelog = prev.callPackage ./go-changelog.nix { };
}

And if we go check the go-protobuf.nix file, we can see it very specifically defines which to pull down

{ buildGoModule, fetchFromGitHub }:

buildGoModule rec {
  pname = "go-protobuf";
  version = "1.5.2";

  src = fetchFromGitHub {
    owner = "golang";
    repo = "protobuf";
    rev = "v${version}";
    sha256 = "1mh5fyim42dn821nsd3afnmgscrzzhn3h8rag635d2jnr23r1zhk";
  };

  modSha256 = "0lnk2zpl6y9vnq6h3l42ssghq6iqvmixd86g2drpa4z8xxk116wf";
  vendorSha256 = "1qbndn7k0qqwxqk4ynkjrih7f7h56z1jq2yd62clhj95rca67hh9";

  subPackages = [ "protoc-gen-go" ];
}

It also includes the SHA checks to make sure that it is precisely the version used—meaning everyone is using exactly the same version.

Conclusion

Nix is a powerful tool to help your developers stay consistent in building environments and keep all your developers on the same page. Although the language is a bit rough to learn, it provides a smooth ride for everyone and reduces the “well it works on my machine” issue that many developers have. Alongside other tools, it can be a great way to reduce downtime due to technical issues and keep working on projects the same across the board.

About the Author

Object Partners profile.
Leave a Reply

Your email address will not be published.

Related Blog Posts
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]
Building Better Data Visualization Experiences: Part 1 of 2
Through direct experience with data scientists, business analysts, lab technicians, as well as other UX professionals, I have found that we need a better understanding of the people who will be using our data visualization products in order to build them. Creating a product utilizing data with the goal of providing insight is fundamentally different from a typical user-centric web experience, although traditional UX process methods can help.
Kafka Schema Evolution With Java Spring Boot and Protobuf
In this blog I will be demonstrating Kafka schema evolution with Java, Spring Boot and Protobuf.  This app is for tutorial purposes, so there will be instances where a refactor could happen. I tried to […]
Redis Bitmaps: Storing state in small places
Redis is a popular open source in-memory data store that supports all kinds of abstract data structures. In this post and in an accompanying example Java project, I am going to explore two great use […]