Achieving Emacs Reproducibility with Nix-Shell
This blog post was written in collaboration with Gemini, an AI from Google. I made only minor adjustments to the text. I think it’s not quite my style, a bit too bombastic for my liking. Is this the average blog post style? |
In my previous post, Serving Internal Website Securely Using Private VPN From Home, I detailed using a Raspberry Pi to host my Emacs Org Mode knowledge base. While the project was a success, it highlighted a recurring challenge: Emacs environment reproducibility.
I encountered two primary hurdles:
Version Fragmentation: The Emacs version in standard Raspberry Pi repositories was outdated and incompatible with the configuration I use on my Ubuntu and Manjaro machines.
Package Drift: The default Emacs package manager pulls the latest available versions for a given release, making it difficult to "freeze" a configuration and ensure identical behavior across different systems.
While I initially used the Snap Store as a workaround on the Raspberry Pi, it only solved the binary versioning, not the underlying dependency management. To bridge this gap, I have turned to nix-shell. By pinning specific Nixpkgs revisions, I can guarantee that the Emacs binary and system dependencies remain identical across every node in my network.
It is important to note that this solution currently pins the Emacs application itself. Since I am still using the default Emacs package manager, internal Emacs package drift is not yet addressed by this Nix configuration. However, having a consistent binary foundation is the first critical step toward a fully reproducible environment. |
The shell.nix Configuration
The following shell.nix file serves as the blueprint for my environment. It pins the package set to a specific GitHub revision to
prevent any unexpected updates:
let
# Pin nixpkgs to a specific revision to ensure exact version reproducibility
nixpkgs = builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/4a1cad4c4625355d5fcc5febbbafe07878a067f2.tar.gz";
sha256 = "0h72dz7cnh2gaszfak3d339f1lqj5d7rwx9gpprdjnwj9d3ckrms";
};
# Import the pinned package set without extra system-level configurations
pkgs = import nixpkgs {
config = { };
overlays = [ ];
};
# Create a shell environment without a C compiler (NoCC) for faster loading
in pkgs.mkShellNoCC {
# Install Emacs and nixfmt into the local shell path
packages = with pkgs; [ emacs nixfmt-classic ];
# Commands to run automatically upon entering the nix-shell
shellHook = ''
emacs --version
emacs &
'';
}Finding the Correct Nixpkgs Revision
Pinning to a specific revision requires a commit hash from the official Nixpkgs repository. While you can browse GitHub manually, the most efficient way to find a specific package version (like Emacs 29.4) is to use the Nix Package Versions search tool.
Visit Nix Package Versions.
Search for
emacs.Select the version you need (e.g., 29.4).
Copy the revision hash and the
sha256hash provided in the instructions.
Activating the Environment
To enter this environment, navigate to your project directory and run:
nix-shellUpon execution, Nix will fetch the specific version of nixpkgs and isolate Emacs 29.4 from your global system. This ensure that
whether I am working on my Raspberry Pi, my Ubuntu laptop, or my Manjaro desktop, I am launching the exact same Emacs binary.
Conclusion: A Stable Foundation
While this nix-shell setup doesn’t yet manage the internal Lisp packages, it eliminates the Emacs "version fragmentation". By
defining the host environment immutably, I’ve moved from managing individual machine states to a unified foundation. This ensures
that my Org Mode knowledge base is running on a consistent, predictable platform, regardless of the underlying Linux distribution.