6.2 KiB
myst | ||||
---|---|---|---|---|
|
(declarative-reproducible-envs)=
Declarative shell environments with shell.nix
Overview
Declarative shell environments allow you to:
- Automatically run bash commands during environment activation
- Automatically set environment variables
- Put the environment definition under version control and reproduce it on other machines
What will you learn?
In the {ref}ad-hoc-envs
tutorial, you learned how to imperatively create shell environments using nix-shell -p
.
This is great when you want to quickly access tools without installing them permanently.
You also learned how to execute that command with a specific Nixpkgs revision using a Git commit as an argument, to recreate the same environment used previously.
In this tutorial we'll take a look at how to create reproducible shell environments with a declarative configuration in a {term}Nix file
.
This file can be shared with anyone to recreate the same environment on a different machine.
How long will it take?
30 minutes
What will you need?
- A basic understanding of the Nix language
Entering a temporary shell
Suppose we want a development environment in which Git, Neovim, and Node.js were installed.
The simplest possible way to accomplish this is via the nix-shell -p
command:
$ nix-shell -p git neovim nodejs
This command works, but there's a number of drawbacks:
- You have to type out
-p git neovim nodejs
every time you enter the shell. - It doesn't (ergonomically) allow you any further customization of your shell environment.
A better solution is to create our shell environment from a shell.nix
file.
A basic shell.nix
file
Create a file called shell.nix
with these contents:
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
pkgs.mkShell {
packages = with pkgs; [
git
neovim
nodejs
];
}
::::{dropdown} Detailed explanation We use a version of Nixpkgs pinned to a release branch, and explicitly set configuration options and overlays to avoid them being inadvertently overridden by global configuration.
mkShell
is a function that produces a shell environment.
It takes as argument an attribute set.
Here we give it an attribute packages
with a list containing one item from the pkgs
attribute set.
:::{Dropdown} Side note on packages
and buildInputs
You may encounter examples of mkShell
that add packages to the buildInputs
or nativeBuildInputs
attributes instead.
nix-shell
was originally conceived as a way to construct a shell environment containing the tools needed to debug package builds.
Only later it became widely used as a general way to make temporary environments for other purposes.
mkShell
is a wrapper around mkDerivation
, so it takes the same arguments as mkDerivation
, such as buildInputs
or nativeBuildInputs
.
The packages
attribute argument to mkShell
is simply an alias for nativeBuildInputs
.
:::
::::
Enter the environment by running nix-shell
in the same directory as shell.nix
:
$ nix-shell
[nix-shell]$
nix-shell
by default looks for a file called shell.nix
in the current directory and builds a shell environment from the Nix expression in this file.
Packages defined in the packages
attribute will be available in $PATH
.
Check that the desired packages are indeed available in the expected version as we did in the previous tutorial.
Environment variables
You may want to automatically export certain environment variables when you enter a shell environment.
Set your GIT_EDITOR
to use the nvim
from the shell environment:
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
pkgs.mkShell {
packages = with pkgs; [
git
neovim
nodejs
];
+ GIT_EDITOR = "${pkgs.neovim}/bin/nvim";
}
Any attribute name passed to mkShell
that is not reserved otherwise and has a value which can be coerced to a string will end up as an environment variable.
:::{dropdown} Detailed explanation
The newly added attribute GIT_EDITOR
is set to a string composed of the output store path of the neovim
derivation and the relative path to the nvim
executable inside that store path.
See the Nix language tutorial on derivations for details.
:::{warning} Some variables are protected from being set as described above.
For example, the shell prompt format for most shells is set by the PS1
environment variable, but nix-shell
already sets this by default, and will ignore a PS1
attribute set in the argument.
If you really need to override these protected environment variables, use the shellHook
attribute as described in the next section.
:::
Startup commands
You may want to run some commands before entering the shell environment.
These commands can be placed in the shellHook
attribute provided to mkShell
.
Set shellHook
to output the current repository status:
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
pkgs.mkShell {
packages = with pkgs; [
git
neovim
nodejs
];
GIT_EDITOR = "${pkgs.neovim}/bin/nvim";
+
+ shellHook = ''
+ git status
+ '';
}
Exit the shell by typing exit
or pressing Ctrl
+D
, then start it again with nix-shell
to observe the effect.
References
mkShell
documentation- Nixpkgs shell functions and utilities documentation
nix-shell
documentation