Creating shell environments#
Overview#
What will you learn?#
How to create and configure reproducible shell environments
How long will it take?#
30 minutes
What will you need?#
A basic understanding of the Nix language
Entering a shell with Python installed#
Suppose we wanted to enter a shell in which Python 3 was installed.
The simplest possible way to accomplish this is via the nix-shell -p
command:
$ nix-shell -p python3
This command works, but there’s a number of inefficiences:
You have to type out
-p python3
every time you enter the shell.It doesn’t scale to an arbitrary number of packages (you would have to type out each package name each time).
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#
The nix-shell
command by default looks for a file called shell.nix
in the current directory and tries to build a shell environment by evaluating the Nix expression in this file.
So, if you properly describe the shell environment you want in a shell.nix
file, you can enter it with just the nix-shell
command without any further arguments.
No more specifying packages on the command line.
Here’s what a basic shell.nix
looks like that installs Python 3 as before:
1let
2 pkgs = import <nixpkgs> {};
3in
4 pkgs.mkShell {
5 packages = [
6 pkgs.python3
7 ];
8 }
where mkShell
is a function that produces a shell environment.
If you save this into a file called shell.nix
and call nix-shell
in the directory containing this shell.nix
file, you’ll enter a shell with Python 3 installed.
Adding packages#
Additional executable packages are added to the shell by adding them to the packages
attribute.
For example, let’s say we wanted to add curl
to our shell environment.
The new shell.nix
would look like this:
1let
2 pkgs = import <nixpkgs> {};
3in
4 pkgs.mkShell {
5 packages = [
6 pkgs.python3
7 pkgs.curl # new package
8 ];
9 }
Note
nix-shell
was originally conceived as a way to construct a shell environment containing the tools needed to develop software; only later was it widely used as a general way to construct temporary environments for other purposes. Also note that mkShell
is a wrapper around mkDerivation
so strictly speaking you can provide any attributes to mkShell
that you could to mkDerivation
such as buildInputs
. However, the packages
attribute provided to mkShell
is an alias for nativeBuildInputs
, so you shouldn’t need to provide both packages
and nativeBuildInputs
.
Environment variables#
It’s common to want to automatically export certain environment variables when you enter a shell environment. For example, you could have a database that depends on an environment variable to set the default authentication credentials during development.
Setting an environment variable in via shell.nix
is trivial.
Any attribute in the mkShell
function call that mkShell
doesn’t recognize as a reserved attribute name will be set to an environment variable in the shell environment.
The attributes that are reserved are listed in the Nixpkgs manual and include packages
, name
, and several others.
Let’s say you wanted to set the database user (DB_USER
) and password (DB_PASSWORD
) via environment variables in your shell.nix
file.
This is how that would look:
1let
2 pkgs = import <nixpkgs> {};
3in
4 pkgs.mkShell {
5 packages = [
6 pkgs.python310
7 pkgs.curl
8 ];
9
10 env = {
11 # Database credentials
12 DB_USER = "db_user";
13 DB_PASSWORD = "super secret don't look";
14 };
15 }
Warning
Some variables are protected from being overridden via the env
attribute as described above.
For example, the shell prompt format for most shells is set by the PS1
environment variable, but nix-shell
already overrides this by default, and will ignore a PS1
attribute listed in env
.
If you really need to override these protected environment variables you can use the shellHook
feature discussed in the next section and export MYVAR="value"
in the hook script.
In some cases it’s necessary to set environment variables this way, but you should use env
when possible.
Startup commands#
You may want to perform some initialization before entering the shell environment (for example, maybe you want to ensure that a file exists).
Commands you’d like to run before entering the shell environment can be placed in the shellHook
attribute of the attribute set provided to the mkShell
function.
To ensure that a file should_exist.txt
exists, the shell.nix
file would look like this:
1let
2 pkgs = import <nixpkgs> {};
3in
4 pkgs.mkShell {
5 packages = [
6 pkgs.python310
7 pkgs.curl
8 ];
9
10 env = {
11 # Database credentials
12 DB_USER = "db_user";
13 DB_PASSWORD = "super secret don't look";
14 };
15
16 # Set shell prompt format, ensure that 'should_exist.txt' exists
17 shellHook = ''
18 export PS1="\u@\h >>> "
19 touch should_exist.txt
20 '';
21 }
Some other common use cases for shellHook
are:
Initializing a local data directory for a database used in a development environment
Running commands to load secrets into environment variables
Installing pre-commit-hooks
Where to next?#
Nixpkgs shell functions and utilities documentation