March 29, 2026

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.

  1. Visit Nix Package Versions.

  2. Search for emacs.

  3. Select the version you need (e.g., 29.4).

  4. Copy the revision hash and the sha256 hash provided in the instructions.

Activating the Environment

To enter this environment, navigate to your project directory and run:

nix-shell

Upon 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.

Tags: Emacs