Why Nix Is Useful: A Minimal Example
A practical solution to common development environment problems
Nix is a package manager and programming language for creating system configurations, developer environments, and building software in a reproducible way. It is intimidating to learn and its value proposition is not clear to most developers. I believe a simple and practical example for why Nix is useful does not exist and this article aims to fix that shortcoming.
The Problem
Consider Jack, a full-stack developer. He just cracked open his brand-new MacBook Pro and he’s eager to start building that one project he’s been thinking about for a while. He wants to use Python for the backend and Postgres as the database.
He starts by creating a new Python project, a virtual environment, and installing psycopg2:
$ mkdir my-project && cd my-project
$ touch main.py
$ python -m venv .venv
$ source .venv/bin/activate
$ pip install psycopg2The last step results in an error:
Error: pg_config executable not found.What happened? Something called pg_config was not found.
What is it? Some binary that’s included with Postgres distributions.
At this point Jack has several options, but they all necessarily involve installing Postgres. But Jack might not want to do that on his Mac for a variety of reasons:
Perhaps he’s going to use something like Supabase or Neon as the dev database and does not need a local instance
He wants to run Postgres via a Docker container
Or he simply doesn’t want to bog down his new computer with Postgres just to be able to compile psycopg2
He should be able to `pip install` this database driver and move onto coding. So what can be done?
The Solution
Turns out it’s possible to create a hermetic environment with any set of packages, libraries, or dependencies, that is unique to a project. This is where Nix comes in.
Skipping over the details of Nix installation1, we can create a minimal development shell using Nix that will include all the dependencies we need for our project:
# flake.nix -- create this file in the project's root folder
{
description = "Python Development Environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.python311
pkgs.postgresql_16
];
};
}
);
}After running `nix develop` (in the project directory) and waiting a few seconds for Nix to download the packages, a developer shell is spawned with Python 3.11 and Postgres 16, including its auxiliary binaries.
Jack tries again to install psycopg2 and no longer sees the pg_config error! But unfortunately, there is a new one:
ld: library not found for -lsslA quick search reveals that this is caused by macOS’s bundled version of OpenSSL, which isn’t compatible with a lot of software. Typically, the solution is to install OpenSSL via Homebrew, which distributes the OpenSSL Foundation’s version of it. In this case the Homebrew version would become the system-wide default, but using Nix we can replace OpenSSL for this project only. It only takes one line:
...
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.python311
pkgs.postgresql_16
pkgs.openssl # <--- one line to add OpenSSL
];
};
...Jack reactivates the development shell and voila: psycopg2 compiles without errors.
Some Concluding Thoughts
There are of course many other ways we could have solved the psycopg2 problem: installing the pre-compiled version, adding Postgres and OpenSSL system-wide via Homebrew, or creating a Docker container with all those things and using it for development.
The goal of this post is not to advocate for Nix over the other methods, but to showcase a minimally useful example of Nix solving a relatable problem. It is easy to see how this solution could be extended to include other libraries or software useful for this project. And if someone else were to start contributing to Jack’s project, all they’d have to do is run `nix develop` to get a working shell with all the required dependencies.
I hope folks will find this example useful to understand how they specifically can benefit from learning and using Nix.
Bonus: Understanding the Nix Language
The Nix language contributes to the steep learning curve of getting started. It’s a functional language, so folks with functional programming experience (not me!) will have an easier time understanding. But for everyone else, here’s a syntactic breakdown for (most of) the flake we wrote above:
Functions in nix are declared with the following syntax:
function_argument: function_bodyFunctions can only take 1 argument and if we want to create one that takes more we have to use currying.
Functions are invoked with the following syntax:
function_name function_argumentIn our flake we invoke the `eachDefaultSystem` and `mkShell` functions.
For more information on the Nix language, check out this summary.
On macOS, I recommend installing Nix via the Determinate Systems Installer, which in addition to installing Nix provides a sane default configuration for it (such as enabling the new nix command and nix flakes)


