Nix on Shani OS — The Best Package Manager for an Immutable System

Nix and immutable Linux were made for each other. Nix's core property — packages are identified by a cryptographic hash of everything that went into building them, stored in an append-only content-addressed store, never modified in place — is exactly what an immutable OS wants in a package manager. Nothing in /nix/store is ever overwritten. Multiple versions of the same package coexist without conflict. A package installed yesterday is still there, byte for byte, regardless of what else you install or remove.

On Shani OS, this works especially well because the @nix Btrfs subvolume is shared between both OS slots. Packages installed while running @blue are equally available when you boot @green after an update. Nix packages survive OS updates and rollbacks completely untouched.

This post covers the practical setup and day-to-day use of Nix on Shani OS. Full reference: docs.shani.dev — Nix Package Manager.

---

Setup (Five Minutes)

Nix is pre-installed on Shani OS. The nix-daemon.socket is enabled at boot and the @nix subvolume is ready. The only step required before installing packages is adding a channel:

nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
nix-channel --update

That is it. After this, nix-env -i <package> installs any package from Nixpkgs — currently over 100,000 packages.

---

Installing Packages

# Search for packages
nix-env -qaP | grep nodejs

# Install by attribute path (more precise)
nix-env -iA nixpkgs.nodejs_22
nix-env -iA nixpkgs.rustup
nix-env -iA nixpkgs.python312

# Install by name (less precise, installs first match)
nix-env -i ripgrep
nix-env -i fd
nix-env -i bat
nix-env -i htop
nix-env -i gh       # GitHub CLI

# Install multiple at once
nix-env -i ripgrep fd bat eza fzf

Installed packages are added to ~/.nix-profile/bin which is on your $PATH. They are available immediately after installation in a new shell.

---

Why Multiple Versions Work

Nix does not install packages to /usr/local/bin. It installs them to /nix/store/<hash>-<name>-<version>/ — where <hash> is a cryptographic hash of the entire build closure (all inputs, dependencies, build flags, patches). Two different versions of Node.js have different hashes and live in different directories. They can coexist without any path conflict.

Your active profile (~/.nix-profile) is a symlink tree pointing to whichever versions you have selected. Switching between versions is changing which symlinks the profile contains.

# Install Node 18 and Node 22 simultaneously
nix-env -iA nixpkgs.nodejs_18
nix-env -iA nixpkgs.nodejs_22

# See all installed packages and which version is active
nix-env -q

For per-project version management, nix-shell is the right tool — see below.

---

Per-Project Environments with nix-shell

nix-shell starts a shell with exactly the packages you specify, without installing them to your profile. When you exit the shell, your environment is unchanged.

# A shell with Node 18 and yarn — for one project
nix-shell -p nodejs_18 yarn

# A Python 3.11 data science environment
nix-shell -p python311 python311Packages.numpy python311Packages.pandas jupyter

# Rust development environment
nix-shell -p rustup gcc

For reproducible project environments, create a shell.nix in your project directory:

# shell.nix
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs_22
    yarn
    typescript
    nodePackages.eslint
  ];

  shellHook = ''
    echo "Node $(node --version), Yarn $(yarn --version)"
  '';
}

Then run nix-shell from the project directory. Anyone with Nix installed gets the exact same environment, regardless of what they have globally installed.

---

Rollback

Every nix-env operation creates a new profile generation. You can roll back any operation:

# List all generations
nix-env --list-generations

# Roll back one step
nix-env --rollback

# Roll back to a specific generation
nix-env --switch-generation 12

# Undo the last install (same as rollback)
nix-env -e <package>  # just uninstall it

This rollback is independent of Shani OS's shani-deploy -r. OS rollback and Nix rollback are separate concerns: OS rollback switches the entire system image; Nix rollback changes which packages are in your profile. Neither affects the other.

---

Nix and the @nix Subvolume

On Shani OS, /nix is mounted from the @nix Btrfs subvolume, which is shared between @blue and @green. This has an important consequence: the Nix store is the same regardless of which OS slot you are running.

When you perform an OS update with shani-deploy, the active slot changes. The @nix subvolume does not change. Your installed packages are immediately available in the new slot without reinstalling anything.

When you perform an OS rollback with shani-deploy -r, same thing — @nix is untouched.

Btrfs deduplication via bees also benefits the Nix store. The Nix store intentionally shares build outputs across packages via hardlinks; bees additionally deduplicates at the block level. The result is an efficient store even with many packages and versions installed.

# Check Nix store size
sudo compsize /nix

# Check disk usage
du -sh ~/.nix-profile /nix/store
nix-store --optimise  # run manual store optimisation

---

Keeping Nix Packages Updated

# Update the channel index
nix-channel --update

# Upgrade all installed packages to latest versions
nix-env -u '*'

# Upgrade a specific package
nix-env -u nodejs_22

# See what would be upgraded without doing it
nix-env -u '*' --dry-run

Unlike pacman -Syu on a traditional system, nix-env -u does not touch the OS. It only updates packages in your profile. If an update breaks something, nix-env --rollback undoes it.

---

Garbage Collection

Nix never deletes a package until you explicitly run the garbage collector. Old generations and unreferenced packages accumulate in /nix/store.

# Delete all old generations (keeps current)
nix-env --delete-generations old

# Delete generations older than 30 days
nix-env --delete-generations 30d

# Run the garbage collector (removes anything not referenced by a live generation)
nix-store --gc

# Combined: clean up old generations and collect garbage
nix-collect-garbage -d

A periodic nix-collect-garbage -d in a cron job or systemd timer keeps the store size manageable.

---

Practical Examples

Setting up a Go development environment

nix-env -iA nixpkgs.go nixpkgs.gopls nixpkgs.golangci-lint
# go, gofmt, gopls, and the linter are available immediately

Setting up Rust

nix-env -iA nixpkgs.rustup
rustup toolchain install stable
rustup component add rust-analyzer

Data science and ML

nix-shell -p python312 python312Packages.numpy python312Packages.pandas \
    python312Packages.scikit-learn python312Packages.matplotlib jupyter

DevOps tools

nix-env -iA nixpkgs.kubectl nixpkgs.helm nixpkgs.terraform \
    nixpkgs.awscli2 nixpkgs.google-cloud-sdk

The Full Ecosystem

For packages not in Nixpkgs, or for specific workflows, the other layers on Shani OS fill the gaps:

Distrobox — for anything requiring a full mutable Linux environment (building AUR packages, running tools that expect a traditional /usr layout, legacy software with complex system deps):

distrobox create --name dev --image archlinux:latest
distrobox enter dev
# Full pacman, yay, anything

Distrobox containers live in @containers and survive OS updates. Guide: Distrobox on Shani OS.

Flatpak — for GUI desktop applications. Flathub has thousands of apps, auto-updates, and sandbox permission management via Flatseal. Guide: Flatpak on Shani OS.

Snap — when an app is only on the Snap Store and not Flathub or Nixpkgs. Pre-configured with its own @snapd subvolume. Guide: Snap on Shani OS.

AppImage — portable self-contained executables for one-off tools or latest beta versions. Gear Lever (pre-installed) integrates them into your launcher. Guide: AppImage on Shani OS.

Podman — OCI containers for services and databases. Docker-compatible, rootless. Guide: Podman on Shani OS.

Homebrew — if you are coming from macOS and brew install is muscle memory, it works identically on Shani OS, installing to /home/linuxbrew/.linuxbrew outside the read-only root. See Homebrew on Shani OS.

Virtual Machines — when you need full hardware-level isolation: a separate kernel, a Windows VM, or GPU passthrough. virt-manager is pre-installed on the KDE Plasma edition. Guide: Virtual Machines on Shani OS.

---

Resources

---

Built in India 🇮🇳 · Immutable · Atomic · Zero Telemetry