Check out FlakeHub — the best place to discover and publish Nix flakes, from Determinate Systems.

Sign up for the Determinate Systems mailing list

* indicates required

3. Explore Nix development environments

Quick start / Explore Nix development environments
Guide 3 of 8
In this guide

Use the nix develop command to activate a Nix development environment

Run a command inside a development environment without actually entering that environment

Explore Nix development environments tailored to specific programming languages

Explore a more mixed development environment

Use nix develop to activate an environment defined in a local flake

One of Nix’s key features for developing software is Nix development environments. You can define development environments of any complexity using the Nix language. We’ll cover that a bit later, but for now let’s get a feel for what a Nix development environment is and how it works.

The nix develop command activates a Nix environment:

Enter a Nix development environment defined in an external flake
nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example"
This could take a while

The first time you activate a Nix development environment using nix develop it’s likely to be a slow operation. That’s because Nix needs to build every package included in the environment from scratch. This is in contrast to most package managers, which install things more quickly because they download pre-built archives like tarballs. Future nix develop invocations should be much faster, as Nix doesn’t need to build the packages again.

You should be greeted by a new shell prompt, something like this:

Shell prompt in the Nix development environment
(nix:zero-to-nix-env) bash-5.2$

:rocket: Success! You’re now in a Bash environment that includes curl and Git. You may already have both in your environment, but run these commands to see that something new is happening:

Ensure that curl and Git are installed
type curl
type git

For curl, for example, you should see a strange path like this (the hash part should be different on your machine):

What happened here? The Nix CLI did a few things:

  • It used the https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example flake reference to pull in some Nix code and built a specific flake output (more on this later).
  • It built the packages specified in the environment configuration (again, more on this later).
  • It set up an environment with a PATH that enables the git and curl packages to be discovered in the Nix store.

Two other things that you can provide in Nix development environments:

  1. Although this example doesn’t include one, you can define shell hooks, which are arbitrary shell code that runs whenever the environment starts up. Some example use cases for shell hooks:

    • echo information about the environment to the console whenever the environment is activated

    • Run things like checks and linters

    • Ensure that other desired hooks, like Git hooks, are properly set up. Run this to see an example shell hook:

      Run a shell hook
      nix develop "github:DeterminateSystems/zero-to-nix#hook"
  2. Nix development environments support environment variables as well. Run echo $FUNNY_JOKE to access a (hilarious) value that’s available only in the Nix environment. Some example use cases for environment variables:

    • Set logging levels using LOG_LEVEL or whatever is appropriate for the tools you’re using.
    • Set the environment using variables like NODE_ENV (for Node.js) to development, dev, and so on.
      Terminal window
      nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#hook"

Let’s leave the Nix development environment to prepare for the next section:

Leave the environment
exit

If you have Git installed, check your PATH for it using type git. It should be at a global path like /usr/bin/git. And if you run echo $FUNNY_JOKE again you should get an empty string (unless you happen to have that variable set on your machine!).

Run commands inside the development environment

While it’s fun to explore the environment, you don’t always want to be inside the environment to use it. The nix develop command provides a --command (or -c) flag that you can use to run commands that use the environment but from your current environment. Here are some examples for the environment we used earlier:

Layer multiple environments
nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example" --command git help
nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#example" --command curl https://example.com

In both cases, you’re running a package in the Nix store and nothing from your global environment. As you can see, Nix development environments are hermetic in that they’re isolated from the surrounding environment (such as your environment variables and paths like /bin and /usr/bin).

Language-specific environments

As we did in the last section, let’s get a bit more specific and explore how Nix can benefit more specific programming environments. Select one of these programming languages:

Select your language

Now explore the Nix development environment for :

First, let’s see the Nix store path for CMake:

See the store path for CMake
type cmake

Check the current CMake version:

Check CMake's version
cmake --version

First, let’s see the Nix store path for the Glasgow Haskell Compiler (GHC):

See the store path for GHC
type ghc

Check the current GHC version:

Check the GHC's version
ghc --version

First, let’s see the Nix store path for Node.js:

See the store path for Node.js
type node

Now use Node to run a program:

Check the Node.js version
node --eval "console.log('1 + 1 = ' + (1 + 1))"

First, let’s see the Nix store path for Python:

See the store path for Python
type python

Now use Python to run a program:

Use Python to run a simple program
python -c "print(1 + 1)"

First, let’s see the Nix store path for the Go CLI:

See the store path for Go
type go

Now check the Go version:

Check the Go version
go version

You should get 1.22.5.

First, let’s see the Nix store path for cargo:

See the store path for Cargo
type cargo

Now create a Rust project in the current directory and run the example:

Initialize a Rust project and run it
cargo init ./zero-to-nix-rs
cd ./zero-to-nix-rs
cargo run

You should see Hello, world!.

First, let’s see the Nix store path for sbt:

See the store path for sbt
type sbt

Check the sbt version inside the environment:

Check the sbt version
sbt --version

Like usual, run exit to leave the Nix environment and return to your usual environment.

Beyond language-specific environments

In the previous section, we explored Nix environments tailored to specific programming languages. But Nix environments are infinitely flexible, enabling you to combine whichever packages you like. Let’s explore an example of this:

Activate a multi-language development environment
nix develop "https://flakehub.com/f/DeterminateSystems/zero-to-nix/*#multi"

This Nix environment has several tools available:

As in the previous examples, you can run commands like type python and type kubectl to see that these tools are all discoverable in the Nix store and not somewhere like /usr/bin. This list could easily include 100 packages. It’s up to you. We won’t cover how to create these environments just yet, but we hope that you come away from this guide with a basic sense of what Nix development environments provide.

Nix development environments and direnv

direnv is a popular tool that automatically loads specific environment variables whenever you cd into a directory (and then unloads those variables when you cd out of the directory). The combination of direnv and Nix can be quite powerful, enabling you to automatically load Nix development environments whenever you navigate to a directory. For more info, see Effortless dev environments with Nix and direnv on the Determinate Systems blog.

From a local flake

Earlier in this guide, we activated a Nix development environment defined in a flake on FlakeHub. While using an environment in this way is helpful, it’s more common to use a development environment defined in a local flake in the current directory.

First, tell us which language you prefer:

Select your language

To get started in your project, create an empty directory and initialize a flake template:

Once the template has been initialized, run ls . to see the contents of the directory, which should include two important files:

  • The flake.nix file defines the flake for your project.
  • The flake.lock pins all of the flake inputs—essentially the Nix dependencies—in your flake.nix file to specific Git revisions.

One of the flake outputs of this Nix flake is a development environment for . To enter that development environment:

Terminal window
nix develop

Now that we’ve entered the development environment, we can do some exploring, starting with Nix store paths.

Ordinarily when you run type gcc on a Unix system, you get a path like /usr/bin/gcc. Try running it in the Nix development environment:

Terminal window
type gcc

You should see a (rather strange) path like this:

Terminal window
gcc is /nix/store/nbrvvx1gyq3as3ghmjz62wlgd8f3zfpf-gcc-wrapper-11.3.0/bin/gcc

Ordinarily when you run type ghc on a Unix system, you get a path like /usr/bin/ghc. Try running it in the Nix development environment:

Terminal window
type ghc

You should see a (rather strange) path like this:

Terminal window
ghc is /nix/store/f3qnvw5gxgxxpr275kf97pfcy2n1gv79-ghc-9.2.4/bin/ghc

Ordinarily when you run type node on a Unix system, you get a path like /usr/bin/node. Try running it in the Nix development environment:

Terminal window
type node

You should see a (rather strange) path like this:

Terminal window
node is /nix/store/i88kh2fd03f5fsd3a948s19gliggd2wd-nodejs-18.12.1/bin/node

Ordinarily when you run type python on a Unix system, you get a path like /usr/bin/python. Try running it in the Nix development environment:

Terminal window
type python

You should see a (rather strange) path like this:

Terminal window
python is /nix/store/cqbfx55481irqgbl3bw8jwf69vjpbp8r-python3-3.9.15/bin/python

Ordinarily when you run type go on a Unix system, you get a path like /usr/bin/go. Try running it in the Nix development environment:

Terminal window
type go

You should see a (rather strange) path like this:

Terminal window
go is /nix/store/5bcx8rv6sy33xsf5dzkp9q8lfdqrsiwa-go-1.19.4/bin/go

Ordinarily when you run type cargo on a Unix system, you get a path like /usr/bin/cargo. Try running it in the Nix development environment:

Terminal window
type cargo

You should see a (rather strange) path like this:

Terminal window
cargo is /nix/store/zc1nr87147gvmg5nqci8q5cfnzg82vwp-rust-default-1.64.0/bin/cargo

Ordinarily when you run type sbt on a Unix system, you get a path like /usr/bin/sbt. Try running it in the Nix development environment:

Terminal window
type sbt

You should see a (rather strange) path like this:

Terminal window
sbt is /nix/store/p0hca7x8g45p5hnh0xjzy5s2bcpy1i9l-sbt-1.7.3/bin/sbt

Probably not what you expected! What happened here? A few things:

  • Nix looked at the devShells flake outputs in flake.nix to figure out which Nix packages to include in the development environment (Nix specifically looked at the packages array).
  • Nix built the packages specified under packages and stored them in the Nix store under /nix/store.