nix run and nix develop — Try Anything Without Installing It

6 min read

Every package manager works the same way. You hear about a tool, you install it, you try it, and then you either keep it or forget to uninstall it. Six months later you’re staring at brew list wondering what half of these things are and whether removing them will break something.

Nix inverts this. The default is impermanence. You run a program, it runs, and then it’s gone from your PATH. No install step, no uninstall step, no residue. If you want to keep it, that’s a separate, deliberate decision.

This is one of those features that sounds like a minor convenience until you use it for a week and can’t go back.

Tier 1: one-shot commands from nixpkgs

The simplest form. You want to run a program — once, right now, without thinking about it:

# Try btop without installing it
nix run nixpkgs#btop

# Try the Helix editor
nix run nixpkgs#helix

# Need jq for one pipeline?
nix run nixpkgs#jq -- '.[] | .name' < data.json

# Check disk usage with dust instead of du
nix run nixpkgs#dust

# Try ripgrep before deciding if it replaces grep
nix run nixpkgs#ripgrep -- "pattern" ./src

# Quick look at a CSV file
nix run nixpkgs#visidata -- data.csv

# Need to convert an image, once
nix run nixpkgs#imagemagick -- convert input.png -resize 50% output.png

# Quick HTTP server for the current directory
nix run nixpkgs#python3 -- -m http.server 8080

The -- separates Nix’s flags from the program’s flags. Everything after -- gets passed through to the program itself.

First run fetches and caches the package. Second run is instant — the binary is already in the Nix store. But it never appears in which, never shows up in your package list, never conflicts with anything. It’s there when you ask for it and invisible when you don’t.

The nixpkgs# prefix means “from the nixpkgs flake.” That’s the same package set behind apt, brew, and every NixOS system — about 100,000 packages. If it’s packaged for Linux, it’s probably in nixpkgs.

Tier 2: running directly from GitHub

Any project that ships a flake.nix with a packages or apps output can be run straight from its repository. No clone, no build setup, no README scavenger hunt:

# Ghostty — GPU-accelerated terminal emulator
nix run github:ghostty-org/ghostty

# OpenCode — terminal-based AI coding assistant
nix run github:anomalyco/opencode

# NixVim — full Neovim distribution configured with Nix
nix run github:nix-community/nixvim

You can pin to a branch, tag, or commit:

# Specific tag
nix run github:ghostty-org/ghostty/v1.0.0

# Specific branch
nix run github:ghostty-org/ghostty/main

# Specific app from a multi-output flake
nix run github:org/repo#specific-app

The URL anatomy, for reference:

nix run github:<owner>/<repo>/<ref>#<app>
         │       │      │     │      │
         │       │      │     │      └── flake app output (optional, defaults to "default")
         │       │      │     └── branch, tag, or commit (optional)
         │       │      └── repository name
         │       └── GitHub user or org
         └── flake URL scheme

This is genuinely powerful. Someone posts a link to a project, you nix run it, and thirty seconds later you’re using it. No installation instructions, no dependency conflicts, no “works on my machine.” The flake defines exactly what gets built and how.

Tier 3: full dev environments with nix develop

nix run executes a single program. nix develop drops you into the project’s entire development environment — compilers, libraries, tools, environment variables, the lot.

You can do this without even cloning the repo:

# Enter the dev shell of a GitHub project directly
nix develop github:ghostty-org/ghostty

# Specific dev shell from a multi-shell flake
nix develop github:org/repo#shell-name

Or from a local checkout, which is the more common case:

git clone https://github.com/user/my-project && cd my-project
nix develop

Inside the shell, everything the project’s flake.nix declares is available. The right compiler version, the right formatter, the right linter, any helper scripts — all in PATH. Leave the shell and they vanish. No global state touched.

This is what makes onboarding to a Nix-based project feel like cheating. No “install these seven things first” document. No version mismatches. nix develop, and you’re ready. If it built on CI, it builds on your machine, because it’s the same environment.

direnv: the invisible version

Typing nix develop every time you cd into a project gets old. direnv automates it. Add a .envrc to the project root:

use flake

Now the dev shell activates when you enter the directory and deactivates when you leave. No manual shell entry, no remembering which project needs what. Your terminal just has the right tools available, always.

This combines especially well with nix-direnv, which caches the dev shell so re-entering the directory doesn’t trigger a rebuild. First entry takes a few seconds. Every entry after that is instant.

nix shell vs nix run

These two look similar but serve different purposes:

# nix run: executes the program's default command, then you're done
nix run nixpkgs#htop

# nix shell: adds packages to PATH, then drops you into a shell
nix shell nixpkgs#imagemagick
convert input.png output.jpg   # 'convert' is now in PATH
mogrify -resize 50% *.png      # so is every other imagemagick command
exit                           # leave the shell, they're gone

nix run is for “I want to use this program right now.” nix shell is for “I need these tools available for a while.” You can also combine packages in a single shell:

nix shell nixpkgs#ffmpeg nixpkgs#imagemagick nixpkgs#jq

Three packages, one ephemeral environment. Useful when you’re doing some ad-hoc media processing and need a few tools together without setting up a flake.

Comma: the laziest possible workflow

If even nix run nixpkgs# is too much typing, comma exists:

# Install comma once
nix profile install nixpkgs#comma

# Then just prefix any command with a comma
, btop
, dust ./
, cowsay "hello from nix"

Comma looks up the command in a nix-index database, finds the package that provides it, and runs it. No nixpkgs# prefix, no knowing the package name — just the command you want.

The trade-off is that you need to build the nix-index database first (nix run nixpkgs#nix-index), and it can return the wrong package if multiple packages provide the same command name. But for casual use — “I want to try that tool I saw on Hacker News” — it’s hard to beat.

The try-before-you-install workflow

Put it all together and you get a fundamentally different relationship with your tools:

  1. Discover — hear about a tool, nix run nixpkgs#tool to try it
  2. Evaluate — use it for a few days via nix shell or comma
  3. Commit — if it’s worth keeping, add it to your NixOS configuration or home-manager
  4. Or don’t — do nothing, the cached build gets garbage-collected eventually

The default is impermanence. You only permanently install things you’ve already decided are worth it. There’s no “I installed this six months ago and forgot” because you never installed it in the first place.

This also changes how you think about tool recommendations. Someone says “try hyperfine for benchmarking”? That’s a ten-second experiment, not a commitment. nix run nixpkgs#hyperfine -- --warmup 3 'my-command' — either it’s useful or it isn’t, and either way your system is unchanged.

When it won’t work

Not everything is nix run-able:

  • Libraries, fonts, and packages without executables — there’s nothing to run. Use nix shell or nix build instead.
  • Programs that need system integration — a window manager, a display server, things that need to register with D-Bus or set up system services. These need proper installation.
  • Projects without a flake.nix — the github: URL scheme requires a flake. An increasing number of projects have one, but plenty don’t. For non-flake Nix projects, you can sometimes use --impure or nix-shell -p, but that’s a different workflow.
  • Large builds from sourcenix run github:some/project might need to compile the entire thing if there’s no binary cache. That’s not a quick experiment, that’s a coffee break. Projects that publish to cachix or the NixOS binary cache avoid this.

These are real limitations, but they’re the edges. For the vast majority of CLI tools, utilities, and development environments, the workflow just works. And once you’re used to it, going back to brew install / brew uninstall / brew cleanup feels like paperwork.