Why I use Nix and make(1) to develop

🗒️ Initially, this blog post was called “Developing with Nix and make(1)”, however, I thought to rename it after a brief discussion with soywod on using Nix as the solo build system.

Today, Magnus, a friend of mine, posted a love letter to make. In his post he talks about how powerful and reliable make(1)1 is, I really recommend you go there and read it too!

In the meantime, he also posted his blog post to lobste.rs. It was a really interesting thread as people discussed from alternatives (such as just for a command runner) to lesser known features as running python as the default shell. I went ahead and made a comment about using Nix and Makefiles as development tools and u/dkl made a question that really made me think:

I don’t understand why Nix alone isn’t sufficient. Can you elaborate on your setup a little?


I admit that my message was not clear: “using make(1) with Nix to make dependencies easier to deal with”. When I wrote this, I was thinking about the concept of prerequisites make has on its rules. Which is nothing more than “in order to generate this file, this other thing has to be built successfully”.

Meanwhile, this made me wonder where I found Nix to not be sufficient on my setup, more specifically, on my blog setup. As mentioned down the same thread, this blog has a relatively simple build process:

  1. Content is written on a Org file
  2. GNU Emacs transforms this file into multiple hugo-compliant Markdown files
  3. Hugo generates a website from the Markdown files
  4. A gzipped archive is generated
  5. The archive is sent to sourcehut pages

If each step was represented in a shell line, it would look like this:

# edit the Org file
# generate Markdown files
emacs $(pwd) --batch -load export.el
# generate website
# create gzipped archive
tar -cvzf site.tar.gz -C public .
# publish website to sourcehut pages
hut pages publish site.tar.gz --domain glorifiedgluer.com --not-found 404.html

Building and publishing with Nix

This is the perfect scenario for Nix as I don’t have any external dependencies to be fetched during the build steps. OK, how would one build the website with Nix? It would probably look like this:

packages.website = stdenv.mkDerivation {
  name = "glorifiedgluercom";
  src = lib.cleanSource ./.;

  buildInputs = [

  configurePhase = ''
    emacs $(pwd) --batch -load export.el

  buildPhase = ''
    tar -cvzf site.tar.gz -C public .

  installPhase = ''
    mkdir -p $out
    cp -r site.tar.gz $out

Running nix build .#website on this derivation would get you a site.tar.gz file in a result directory. More than just declaring the build steps, this will also ensure that all the dependencies needed to build the website would be the same byte-by-byte.

What about the developing experience?2 How would I run hugo serve to get a feedback loop while I write and how would I publish it? You can tell that my feedback loop is not going to happen inside that derivation above as I have to keep running nix build in order to see how things look like. Fortunately, this is easy to fix with nix run.

# this is using the helper function from github:numtide/flake-utils
apps.run = utils.lib.mkApp {
  drv = pkgs.writeShellScriptBin "run" ''
    ${emacs}/bin/emacs $(pwd) --batch -load export.el
    ${hugo}/bin/hugo serve

Now I can just run the command nix run .#run and I’ll have hugo serving my website locally. The only missing step now is publishing it with hut, the sourcehut CLI.

apps.publish = utils.lib.mkApp {
  drv = pkgs.writeShellScriptBin "publish" ''
    ${hut}/bin/hut pages ${website} site.tar.gz \
        --domain glorifiedgluer.com \
        --not-found 404.html

Running nix run .#publish will now publish the website to sourcehut pages. Note that I’m using the derivation we created above, this will ensure that Nix actually built the package before publishing. How cool is that? It’s really cool, but we are missing something here: dependencies!

Makefile to the rescue

As previously mentioned, make has a concept of prerequisites. This is exactly what we need to ensure all dependencies are met before publishing the website.

Makefiles have something called target, it is supposed to be mapped to a file. If not, it is called a PHONY target. You can understand PHONY targets as command runners. Let’s write our Makefile to take care of our website build steps:

all: publish

	emacs $(pwd) --batch -load export.el

public: content

site.tar.gz: public
	tar -cvzf site.tar.gz -C public .

.PHONY: publish
publish: site.tar.gz
	hut pages publish site.tar.gz \
		--domain glorifiedgluer.com \
		--not-found 404.html

.PHONY: run
run: content
	hugo serve

As you can see, we have targets such as public and content defined in our Makefile, this means that make will generate the public directory if it notices that something changed. Our publish target is also interesting, it takes site.tar.gz as a prerequisite, and site.tar.gz takes public as one too. This will ensure that every dependency on our build is met before actually publishing.

Leveraging Nix for package dependencies

One of the nicest things Nix provides, if not the nicest one, is the nix develop command. This can be used to give us a shell environment with all the packages needed to build our project.

devShells.default = mkShell {
  buildInputs = [

If you run nix develop, you’ll be thrown at a shell session with these packages on $PATH. However, you can also run nix develop -c <cmd> to run commands without entering the shell. Do you see where we are heading? We can run nix develop -c make and have all the needed packages available for make to use.

user@host:~/glorifiedgluercom$ nix develop -c make
rm -rf content
rm -rf public
rm -rf site.tar.gz
emacs  --batch -load export.el
tar -cvzf site.tar.gz -C public .
hut pages publish site.tar.gz \
	--domain glorifiedgluer.com \
	--not-found 404.html

Published site at glorifiedgluer.com

Wrapping up

After some more thinking about this subject, it was clear to me that using make the way it is presented here might give you the following advantages:

  1. You have a standard way to build your system that Non-Nix users can leverage
  2. It’s relatively easier to reason about the build steps

ON THE OTHER HAND, this also gave me a really interesting idea that got me excited to try. What are the advantages of using Nix as a make replacement?

  1. You have a single tool to manage dependencies and build steps

In this case, build steps is such a broad thing on Nix that it would add a lot more points here and I don’t think it’s fair. You get a discoverable CLI with auto-completion (this is also true for make), caching dependencies/build steps is rather straightforward and you can share everything between your projects with Nix Flakes.

What about the interesting idea? Some time ago I had another idea and wrote about my personal monorepo, this didn’t get too far3. Anyway, most of the problems I faced resolved around tooling and how weird it was to switch between multiple languages and tools. I think, however, that it might be able possible to overcome this with the ideas presented here. Let’s see how it goes! 😊

  1. More specifically, this post is talking about GNU Make↩︎

  2. This is where the discussion with soywod appears here. ↩︎

  3. I intend to write about the reasons in the future. ↩︎

Links to this article

Articles from blogs I follow around the net

Simpler encapsulation with immutability

A worked example. I've noticed that many software organizations struggle with encapsulation with 'bigger' problems. It may be understandable and easily applicable to define a NaturalNumber type or ensure that a minim…

via ploeh blog June 12, 2024

Update on our infrastructure plans

Now that Drew has provided an update on the general state of SourceHut, I would like to follow up with one focusing on our infrastructure. Much has happened under the hood, a lot has not happened, and plans come and go as reality changes its mind every now a…

via Blogs on Sourcehut June 12, 2024

OpenBSD extreme privacy setup

# Introduction This blog post explains how to configure an OpenBSD workstation with extreme privacy in mind. This is an attempt to turn OpenBSD into a Whonix or Tails alternative, although if you really need that level of privacy, use a system from this…

via Solene'% June 10, 2024

Generated by openring