nix-shell vs. nix shell

Exploring Nix is intimidating for a newcomer. It’s a language, an ecosystem, a platform, and a philosophy, but it’s also kind of fractured between classic Nix and Nix flakes. Here’s my short account of using nix-shell and nix shell to create a development environment to build this blog.

Introduction

I recently decided to try out NixOS in an attempt to find a more robust way to manage my Linux development environment. Specifically, I want to have my development virtual machine (VM) be as reproducible as possible (I know, that’s probably an unending rabbit hole), and I want to keep it lean. NixOS will help me to declare and manage my VM’s configuration, and Nix will enable me to install and remove the dependencies I need for a project. For example, Firefox, Neovim, Git, and Hugo are the core software I use when writing a blog post. Firefox and Neovim are used outside of this blog, but I don’t need Hugo laying around when I’m not working on my blog. Hopefully, you get my motivation.

But what about using Docker containers? I know all about it, and I have managed development environments with containers many times before. It generally works fine, but containers can be big and I can’t (or don’t want to) put my whole VM in a container.

It’s the beginning of 2024 and, even though they are still experimental, Nix flakes seems like the way to get started with Nix. After a couple of days of reading about flakes and tinkering with them, I have a working NixOS configuration for my VM. I’m not ready to share my configuration yet, mainly because it’s for my work VM and I use security.pki.certificates to add my company’s intranet certificate authority (CA) to the system’s trusted CAs, and that’s not something that should be shared publicly (obviously).

With a working flake-based NixOS configuration, let’s try to build this blog…

Hugo is Simple Enough, Right?

As stated before, this blog is built with Hugo . After cloning the blog’s repository, the workflow I want is to enter the repository’s local directory, install Hugo, write a post, commit and push it to GitLab, and then uninstall Hugo. Let’s use both classic Nix and Nix flakes to achieve this.

Classic Nix

If I was to be using classic Nix, that is, not using Nix flakes, then I’d run nix-shell -p hugo. I’m still kicking the tires on this whole Nix thing, so I’ll try that just to see what happens.

blog ➤ nix-shell -p hugo

_output omitted for brevity_

[nix-shell:~/dev/personal/blog]$ hugo version
hugo v0.120.3+extended linux/amd64 BuildDate=unknown VendorInfo=nixpkgs

[nix-shell:~/dev/personal/blog]$ exit

blog ➤ nix-store --gc

Okay! I was “dropped” into a new development shell that had hugo installed. Then, I queried the version of Hugo installed, exited the shell, and ran the Nix garbage collector to remove the unused Hugo installation. That was… really easy.

Nix installed version v0.120.3+extended for my platform. This makes sense: I pinned nixpkgs to use nixos-23.11 in my system’s flake.nix (see listing below), and I can see on NixOS Search that this is the version of Hugo in the 23.11 channel .

# ~/nixos-config/flake.nix

inputs = {
  # There are many ways to reference flake inputs (see link above). The most
  # widely used is `github:owner/name/ref`, which represents the GitHub
  # repository URL + branch/commit-id/tag. GitLab repositories can be used
  # simiarly.

  # Official NixOS package source
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";

  # ...
};

Now, let’s do it the “flakes way”.

Nix Flakes

There’s a section in the “NixOS & Flakes Book” that discusses what commands I should be using to accomplish the same thing above with flakes. From that section, I should be using nix develop, nix shell, and nix run instead of nix-shell. That seems excessive. I mean, nix-shell -p hugo was a one liner that did everything I wanted, but okay, I’m here to learn.

Sidebar: I had to google “nix-shell vs nix shell” to try to understand why Nix flakes replaces this one command with three. This post on the NixOS Discourse was helpful: https://discourse.nixos.org/t/nix-shell-nix-shell-and-nix-develop/25964

It looks like nix shell is an easy substitute:

blog ➤ nix shell nixpkgs#hugo

*output omitted for brevity*

blog ➤ hugo version
hugo v0.121.1+extended linux/amd64 BuildDate=unknown VendorInfo=nixpkgs

blog ➤ exit

blog ➤ nix-store --gc

This is the same workflow as before. Let’s quickly note the differences. First, while I like that the new shell uses my zsh setup, it’s not obvious that I was actually “dropped” into a new shell. The command line isn’t prefixed with nix-shell like it was after running nix-shell -p hugo, which was a nice indicator. Second, and more interestingly, a different version of Hugo was installed.

Before diving in further, let’s talk about what the nix-shell command that I typed, er, copied. That’s right, I copied and pasted the nix-shell nixpkgs# part from an example in the book and tacked on hugo to the end of it (because that just seemed like the correct thing to do). I should understand what I just did before continuing.

Spends 30 minutes here and there over several days reading various Nix-related materials. Now you understand why I don’t blog that much. :)

Quoting the Nix Reference Manual, “a flake is a filesystem tree (typically fetched from a Git repository or a tarball) that contains a file named flake.nix in the root directory.”1 Now, nixpkgs, as used above, is clearly not a filesystem tree. It’s actually a symbolic identifier called a flake registry. The next logical question is how can I know the URL mapped to the nixpkgs flake registry. Well, there’s the nix registry command, so let’s use that to list the registries I have:

blog ➤ nix registry list                                                                                                                                                          git:master*

*output omitted for brevity*

global flake:nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable

The nixpkgsflake registry defaults to the nixpkgs repository’s unstable branch. Defaulting anything to a value with the word “unstable” in it sounds risky, but I clearly don’t understand how the registry system is organized. (As an aside, here’s the issue requesting that the default registry change from master to nixpkgs-unstable: https://github.com/NixOS/flake-registry/issues/16 .) Nevertheless, if I go to the nixpkgs-unstable branch of the NixOS/nixpkgs Git repository hosted on GitHub then I should find the Hugo package with Hugo’s version set to 0.121.1+extended…

Ladies and gentlemen, we got him: https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/by-name/hu/hugo/package.nix#L13 . Note that in the flake the buildGoModule’s attribute set has extended in its tags list.

Good for Now

I didn’t get around to nix-develop and nix-run like I wanted, but I’ll forgive myself since this is all new to me. I want to eventually have the flake for my blog to install the same version of Hugo that I use on CI to build and deploy my blog. Conveniently, the version I use is the version that is defined in the nixpkgs registry.

I’ve barely scratch the surface of NixOS and Nix flakes, but I’m interested in learning more. We’ll see what happens.

Fin.

nix