Daily CLI, flakes, and devShells

Updated

September 4, 2026

Daily CLI, flakes, and devShells

Goal: Make Nix on Fedora your default way to enter project toolchains—with flakes, locks, nix develop, and direnv—so the same workflow continues unchanged on NixOS.


Modern CLI map (muscle memory)

Task Command
Build package nix build .#pkg / nix build nixpkgs#hello
Run without installing nix run nixpkgs#cowsay -- hi
Temporary tools nix shell nixpkgs#jq nixpkgs#yq-go
Project shell nix develop
Search nix search nixpkgs ripgrep
Eval nix eval .#foo
Flake show nix flake show
Update inputs nix flake update
Metadata nix flake metadata

Always prefer nix over nix-env / nix-channel for new work.


First project flake on Fedora

mkdir -p ~/lab/nixos-book/nix-on-linux/demo-flake
cd ~/lab/nixos-book/nix-on-linux/demo-flake
git init

flake.nix (pin philosophy: track nixos-26.05 to match this book, or nixpkgs-unstable if you accept drift—commit the lock either way):

{
  description = "Fedora bridge demo flake";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux"; # or: builtins.currentSystem cautiously; prefer explicit
      pkgs = import nixpkgs { inherit system; };
    in {
      packages.${system}.default = pkgs.hello;

      devShells.${system}.default = pkgs.mkShell {
        packages = with pkgs; [
          git
          jq
          ripgrep
          # language example — pick what you actually use:
          go_1_24 # adjust attribute to what exists on your pin
          # python3
          # nodejs_22
        ];
        shellHook = ''
          echo "devShell ready on $(uname -sr) — Nix on Linux bridge"
        '';
      };
    };
}
Note

Attribute names like go_1_24 change across nixpkgs pins. Use nix search nixpkgs go or nix repl to confirm on your lock. The pattern matters more than the exact attribute.

git add flake.nix
nix flake lock
git add flake.lock
nix develop
# inside shell:
jq --version
rg --version
exit

Reproducibility check (second machine / second user):

nix develop --offline   # after first populate, often works if store warm

nix develop vs nix shell vs profile

Lifetime Pinned by Use
nix shell nixpkgs#… Shell only Floating unless flake ref Quick experiments
nix develop Shell only flake.lock Real projects
nix profile install Until removed Weak unless you pin Global personal CLIs

direnv + nix-direnv (high leverage)

Auto-load shells when you cd into a project—same on Fedora and NixOS.

Install tools (profile or HM later)

nix profile install nixpkgs#direnv nixpkgs#nix-direnv

direnvrc

mkdir -p ~/.config/direnv
# Prefer path from nix-direnv package; common pattern:
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' >> ~/.config/direnv/direnvrc

Shell hook

bash (~/.bashrc):

eval "$(direnv hook bash)"

zsh (~/.zshrc):

eval "$(direnv hook zsh)"

Project

cd ~/lab/nixos-book/nix-on-linux/demo-flake
echo 'use flake' > .envrc
direnv allow
cd ..
cd demo-flake   # should load shellHook / PATH

nix-direnv creates GC roots so nix store gc won’t delete your active shell deps mid-week. Review roots periodically.


Ergonomic extras (optional)

nix profile install \
  nixpkgs#nix-output-monitor \
  nixpkgs#nix-tree \
  nixpkgs#nix-your-shell
Tool Role
nom (nix-output-monitor) Readable build logs (nom develop, etc.)
nix-tree Why is the store huge?
nix-your-shell Keep zsh/fish inside nix develop

Example zsh after omz/init:

# if nix-your-shell installed
command -v nix-your-shell >/dev/null && nix-your-shell zsh | source /dev/stdin

Multi-system flakes (preview)

Laptops vary (x86_64-linux, aarch64-linux). Pattern:

outputs = { self, nixpkgs }:
  let
    systems = [ "x86_64-linux" "aarch64-linux" ];
    forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
  in {
    devShells = forAllSystems (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      in {
        default = pkgs.mkShell { packages = [ pkgs.jq ]; };
      });
  };

Same flake later grows nixosConfigurations.myhost = … without changing how devShells work.


CI-shaped local check

# Fail if lock missing or eval broken
nix flake check || nix develop -c true

When you add packaging later, checks become richer (flake checks).


Lab

  1. Convert one real project you maintain to flake.nix + use flake direnv.
  2. Commit flake.lock.
  3. On a clean shell, cd into the project and confirm tools appear via direnv.
  4. Run nix flake metadata and paste the lock refreshed time into your journal.

Checkpoint

  • You use nix develop daily for at least one repo
  • flake.lock is committed
  • direnv loads without manual nix develop
  • Profile remains small

Further depth