Customizing the devShell
The Panfactum stack comes with a preloaded “devShell” that contains both essential and convenience utilities for working with the stack. This shell automatically launches when you open your organization’s stack repository in a terminal.
You can also include additional utilities that are specific to your organization. This guide walks through the most common customization options.
Initial Installation
When you first install the devShell, the flake.nix
in the root of your repository should have the following contents:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; # Utility for generating flakes that are compatible with all operating systems panfactum.url = "github:panfactum/stack/edge.25-02-18"; # Make sure this matches your version of the Panfactum Stack };
outputs = { panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: { devShell = panfactum.lib.${system}.mkDevShell { }; } );}
Adding New Tools
panfactum.lib.${system}.mkDevShell
takes an input called packages
that can take addition nix packages (derivations) to be added to your devShell. There are a few common ways to do this:
From nixpkgs
This search interface is very helpful for finding specific versions of packages that are available in the nixpkgs repository.
nixpkgs is the largest software repository in the world (reference), so an enormous amount of tooling is readily available to you.
To add a package such as nodejs_20
, update your flake.nix
with the following:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; # Utility for generating flakes that are compatible with all operating systems panfactum.url = "github:panfactum/stack/edge.25-02-18"; # Make sure this matches your version of the Panfactum Stack nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; # 'nixos-24.05' can be any ref from the nixpkgs repo https://github.com/NixOS/nixpkgs };
outputs = { nixpkgs, panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; }; }; in { devShell = panfactum.lib.${system}.mkDevShell { packages = [ pkgs.nodejs_20 ]; }; } );}
All packages defined in the nixpkgs
GitHub repo are updated together, and many updates are made to the repository each day. pkgs
refers to the set of packages defined by a specific commit in nixpkgs
. That commit is determined by the nixpkgs.url
field in your flake.nix
.
However, if you specify a branch such as nixos-24.05
(via inputs.pkgs.url = "github:NixOS/nixpkgs/nixos-24.05;"
), the commit hash is pinned in the flake.lock
file and will not be automatically updated when you run nix flake update
.
In general, we recommend pinning the commit directly in the flake.nix
to avoid ambiguity. For example, nixpkgs.url = "github:NixOS/nixpkgs/c8e74c2f83fe12b4e5a8bd1abbc090575b0f7611";
.
If you are not pinning the nixpkgs commit hash in your flake.nix
, running nix flake update
will likely change the versions of the packages that are installed.
You may find that your want to include specific versions of separately tools that are on different commits of nixpkgs from each other. Fortunately, you can very easily include multiple commits of “views” of nixpkgs:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; panfactum.url = "github:panfactum/stack/edge.25-02-18"; nixpkgs1.url = "github:NixOS/nixpkgs/c8e74c2f83fe12b4e5a8bd1abbc090575b0f7611"; nixpkgs2.url = "github:NixOS/nixpkgs/dd53ef232cf8398c6832dcc9e6c4194bf109e70e"; };
outputs = { nixpkgs1, nixpkgs2, panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: let pkgs1 = import nixpkgs1 { inherit system; config = { allowUnfree = true; }; }; pkgs2 = import nixpkgs2 { inherit system; config = { allowUnfree = true; }; }; in { devShell = panfactum.lib.${system}.mkDevShell { packages = [ pkgs1.nodejs_20 pkgs2.python3 ]; }; } );}
Custom Scripts
You may want to include custom shell scripts in your developer environment.
Imagine that you have the following script called foobar.sh
that you want users to be able to call by executing foobar
anywhere in the developer environment:
#!/usr/bin/env bash
echo "Foobar"
You would include this in your flake.nix
as follows:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; # Utility for generating flakes that are compatible with all operating systems panfactum.url = "github:panfactum/stack/edge.25-02-18"; # Make sure this matches your version of the Panfactum Stack nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; # 'nixos-24.05' can be any ref from the nixpkgs repo https://github.com/NixOS/nixpkgs };
outputs = { nixpkgs, panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; }; }; in { devShell = panfactum.lib.${system}.mkDevShell { packages = [ (pkgs.writeShellScriptBin "foobar" (builtins.readFile ./foobar.sh)) ]; }; } );}
Wrapping an Existing Tool
Occasionally, you may want to use a package but automatically pass in default flags or environment variables for users of your developer environment.
There is a handy package called makeWrapper that allows you to do this.
For example, let’s say you want to always add -n cilium
to calls of the cilium
CLI. Simply create the following cilium.nix
file (make sure to git add
it):
{ pkgs }:pkgs.symlinkJoin { name = "cilium-cli"; paths = [ pkgs.cilium-cli ]; buildInputs = [ pkgs.makeWrapper ]; postBuild = '' wrapProgram $out/bin/cilium \ --add-flags "-n cilium" '';}
You would include this in your flake.nix
as follows:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; # Utility for generating flakes that are compatible with all operating systems panfactum.url = "github:panfactum/stack/edge.25-02-18"; # Make sure this matches your version of the Panfactum Stack nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; # 'nixos-24.05' can be any ref from the nixpkgs repo https://github.com/NixOS/nixpkgs };
outputs = { nixpkgs, panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; }; }; in { devShell = panfactum.lib.${system}.mkDevShell { packages = [ (import ./cilium.nix { inherit pkgs; }) ]; }; } );}
Build a Custom Utility
If you are looking to package a custom binary, you will need to learn how nix derivations work.
We strongly encourage you to go through the nix pills tutorial for a good beginner overview.
The nixpkgs manual contains very detailed instructions for best practices in creating nix packages for the most popular language ecosystems.
Adding Additional Shell Hooks
panfactum.lib.${system}.mkDevShell
takes an input called shellHook
that takes an arbitrary bash snippet (as a string) that will be executed every time the devShell loads.
This can be extremely helpful for loading default environment variables, installing pre-commit hooks, issuing warnings, or automatically running any other repository maintenance activities.
You can add the hook as follows:
{ inputs = { flake-utils.url = "github:numtide/flake-utils"; # Utility for generating flakes that are compatible with all operating systems panfactum.url = "github:panfactum/stack/edge.25-02-18"; # Make sure this matches your version of the Panfactum Stack };
outputs = { panfactum, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: { devShell = panfactum.lib.${system}.mkDevShell { shellHook = '' export GREETING="Hello World!" echo "$GREETING" ''; }; } );}
Triggering Automatic Updates
You may find it easier to split out your devShell customizations into multiple files. If you want the devShell to reload automatically when those files change, you need to add new nix_direnv_watch_file
directives to your .envrc
file.
For example, if you want to watch all files in a directory called shell
, your .envrc
file should contain the line
nix_direnv_watch_file shell/*
Additional Flake Customization
While we provide a customized builder for the outputs.devShell
field of your Nix flake, you should feel free to take advantage of the other fields for adding additional functionality. These will not interfere with Panfactum at all.
For example, you can add packages (nix build
), checks (nix check
), and even apps (nix run
).
For more information on flake development, see the flake documentation.