Guix Profiles to logically separate packages

Guix profiles are a way to logically separate and invoke a group of packages. This allows us to use different sets of packages for each use-case: for example a set of applications for work, and a different set - including games - for play. Profiles can be created using manifests which makes it easy to maintain multiple different profiles and to combine them depending on the scenario.

In this post we'll cover the basics of profiles and how they work. The difficult aspect of profiles is that they're most powerful when used in combination with manifests and guix shell environments. To that end, in the next post we'll cover managing and maintaining multiple different profiles so that it's easy to maintain a Guix set-up on multiple machines. Then we'll explore how to use profiles with Guix shell to create different development environments. As we move through these posts we're starting to move beyond basic package management into a wider set of advanced capabilities that Guix provides.

If you'd like to catch-up on the full series:

Understanding profiles

The use of the name Profile isn't a coincidence, because if you think about the purpose of the shells .profile or .bash_profile it is to set-up the environment for a user. That's exactly what a GUIX profile does, it lets us group some packages together and when we source it all the environment variables needed to use the commands are set-up correctly.

A profile consists of two parts:

  • a collection of GUIX packages
  • the environment configuration needed to use them

To understand how a user can have multiple different collections of packages we need to understand how Guix installs and manages packages. Each package is installed into the Guix Store under /gnu/store. An individual package, or version of a package, is installed into a separate directory in the Store. Guix creates soft links from the isolated install directory (/gnu/store/<some-hash>) to a directory under the users control (a profile directory). Generally, we're installing multiple applications, consequently multiple soft links are created.

Lets look at the links that are created: here's the output from listing the default profile (~/.guix-profile) with just the hello package installed:

/home/steve/.guix-profile
├── bin -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/bin
├── etc
│   ├── ld.so.cache -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/etc/ld.so.cache
│   └── profile
├── manifest
└── share
    ├── doc -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/share/doc
    ├── emacs -> /gnu/store/93bv4gh0qbfdaa2jbi70487zbsn1yrcd-emacs-subdirs/share/emacs
    ├── info
    │   ├── dir -> /gnu/store/zgqf3ybi09g0l8s4g03szjyr9dd9c6q0-info-dir/share/info/dir
    │   └── hello.info.gz -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/share/info/hello.info.gz
    ├── locale -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/share/locale
    └── man -> /gnu/store/s5pd3rnzymliafb4la5sca63j86xs0y0-hello-2.12.1/share/man

The hello package has been installed into a hashed directory under /gnu/store and soft links are created for the bin directory where the command is. Notice that it also creates links to other necessary directories and files such as docs and locale.

In Guix we can create multiple profiles because each profile can link to the same hello package in the Store without interfering with another profile's installation. This is the key to how multiple profiles can contain the same version of a package.

If we want to use the hello command we would have to manually call the location (~/.guix-profile/bin/hello) as it's not included in our $PATH, and there's no man page as the system doesn't know about this location either. This is why the second part of a profile is the environment configuration needed to use the installed packages.

Each profile has an /etc/profile file which can be sourced to include its configuration into our existing environment. This will add the profiles bin/ directory to our $PATH, and add any other environment changes needed to use the packages. For example, in a more complex profile here are the environment variables that are set-up:

export PATH="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/bin:${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/sbin${PATH:+:}$PATH"
export GUIX_PYTHONPATH="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/lib/python3.9/site-packages${GUIX_PYTHONPATH:+:}$GUIX_PYTHONPATH"
export XDG_DATA_DIRS="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/share${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"
export JUPYTER_CONFIG_PATH="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/etc/jupyter${JUPYTER_CONFIG_PATH:+:}$JUPYTER_CONFIG_PATH"
export GI_TYPELIB_PATH="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/lib/girepository-1.0${GI_TYPELIB_PATH:+:}$GI_TYPELIB_PATH"
export PASSWORD_STORE_SYSTEM_EXTENSION_DIR="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/lib/password-store/extensions"
export GIT_EXEC_PATH="${GUIX_PROFILE:-/gnu/store/ykjkpqpddj6jcyj8lqqkpnl9aa8hf6l3-profile}/libexec/git-core"

Guix's default profile is normally sourced in .bash_profile, but we could also do it at the command line:

$ source ~/.guix-profile/etc/profile

# the hello command is now in my path
$ hello

Listing Profiles

Before we start creating profiles lets see how we list all the profiles we have:

$ guix package --list-profiles
/home/<user>/.config/guix/current
/home/<user>/.guix-profile

There are two profiles by default. The ~./guix-profile is the default profile that is used when you install packages, it's implicitly used when you call guix package. There's also a specific profile that only has the guix commands in it ~/.config/guix/current, this is implicitly used when you do a guix pull.

We can see that the default ~/.guix-profile is used by the guix command by see that these two command return the same output:

$ guix package --list-generations
[output list of generations]

$ guix package --profile=/home/<user>/.guix-profile
[output same list of generations]

See the Common Workflows - Deleting Generations section for more on the default profile.

Creating a profile

Lets create our first profile.

It's best to store all profiles in a single location and have a subdirectory for each one as this makes them easy to manage. The GNU Guix Cookbook suggests using ~/.guix-extra-profiles which works for me:

export GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles

# create a directory that will store our example1-profile in it
mkdir -p $HOME/.guix-extra-profiles/example1-profile

Profiles and manifests work very well together, making it easy to create and maintain multiple profiles. Lets create a manifest file and call it example1-manifest.scm:

(specifications->manifest
    '(
        ;; glibc-locales is always needed
        "glibc-locales"
        ;; df but with graphs
        "dfc"
        ;; replacement for ls
        "exa"
        ;; system monitoring
        "htop"))

We can then install packages for our profile with something like this:

$ guix package --manifest=/path/to/example1-manifest.scm \
    --profile=$HOME/.guix-extra-profiles/example1-profile/example1-profile

It's not a mis-type that we have example1-profile/example1-profile in the command: we're telling Guix to create the example1-profile and store all the links for it in the example1-profile directory. If you don't do this you land-up with lots of messy links in the .guix-extra-profiles directory - whereas doing it this way we simply delete the example1-profile directory and the profile is removed.

If we look in that directory we'll see something like this:

lrwxrwxrwx 1 steve steve 51 Jan  7 15:25 example1-profile -> example1-profile-1-link/
lrwxrwxrwx 1 steve steve 51 Jan  7 15:25 example1-profile-1-link -> /gnu/store/f8ihjm8m6glxhyclsqkc6bmnlqxhbmpf-profile/
-rw-rw-r-- 1 steve steve  0 Jan  7 15:25 example1-profile.lock

Every time we add a new generation to this profile the link for the profile will be updated. Add a couple of packages to the manifest, install them and see for yourself.

Guix commands and profiles

All the normal guix package commands work, but we also provide a specific profile path, rather than having Guix use the default profile. To do this supply --profile=XXX or -p XXX to the commands. For example:

$ guix package --profile=$HOME/.guix-extra-profiles/example1-profile/example1-profile --list-installed

$ guix package --profile=$GUIX_EXTRA_PROFILES/example1-profile/example1-profile --install wget

$ guix package --profile=$GUIX_EXTRA_PROFILES/example1-profile/example1-profile --list-generations

Activating a Profile

To activate a new profile we source in its /etc/profile. To test this first create a new XTerm window or Tmux split - because you can't easily remove the environment configuration that it adds, so at the end you can just shut the window. We do:

$ export GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles

$ export GUIX_PROFILE=$HOME/.guix-extra-profiles/example1-profile/example1-profile
$ source $GUIX_PROFILE/etc/profile

$ echo $PATH
/home/<user>/.guix-extra-profiles/example1-profile/example1-profile/bin: [etc]

$ which exa
/home/<user>/.guix-extra-profiles/example1-profile/example1-profile/bin/exa

It's important that we alter the GUIX_PROFILE and source it: if you just try sourcing the full path it doesn't work.

Deactivating a Profile

Guix doesn't provide a command for deactivating a profile. That's why for our example above I suggested opening a new XTerm window or Tmux split - as we can just close it.

Guix does provide a system for creating a new environment which is why lots of examples link profiles and environments together.

Profiles and Environments

We'll keep the detail for another time, but Guix provides a way to create a temporary environment (think of it as a chroot or container) with the guix shell command. This command work well with manifests and profiles. And, when we exit the environment we return to our previous shell removing any environment changes that were made by the profile. Here's a few examples:

$ guix shell coreutils

$ guix shell python coreutils --pure
$ echo $PATH
/home/steve/.guix-profile/bin:/gnu/store/zlvmm1fal6rcg601i4wlsbymahjp2qar-profile/bin

The first command creates a basic environment with coreutils package (e.g. ls) added to our existing $PATH: we can use all our normal commands plus the new ones guix has added from coreutils. We can have multiple different packages installed (as the second command shows): importantly in the second command we've added the --pure option which means we only get the environment that Guix is creating, so when we check our $PATH we no longer have our standard command (e.g. /usr/bin etc) directories.

We can also use the shell command to create a separated environment configured by a profile:

$ guix shell --profile=$HOME/.guix-extra-profiles/example1-profile/example1-profile --pure
$ echo $PATH
/home/steve/.guix-profile/bin:/gnu/store/f8ihjm8m6glxhyclsqkc6mnlqxhbmpf-profile/bin
$ exa /gnu/store/f8xxxxx
dfc exa htop

Here we create an shell environment using the example1-profile that we created earlier: as we can see it contains only the three commands that we added through the manifest.

The guix shell command also has the ability to create a separate container:

$ guix shell --profile=$HOME/.guix-extra-profiles/example1-profile/example1-profile --container

The nice thing is that every time we drop into the new environment with guix shell we know it's completely clean and reproducible as it's using the profile we created with the manifest. There's lots of great use-cases for this including project specific development environments.

Updating a profile

To update and upgrade a profile we do a single guix pull to update the package definitions from the repository for the user. Then for each profile that we have we do a guix upgrade:

$ guix pull

$ guix upgrade --profile=$GUIX_EXTRA_PROFILES/example1-profile/example1-profile

This will upgrade the profile to the latest packages. If we're using a manifest (as above), then we want reproducibility so we can simply do a guix pull and then reinstall using the manifest as this will install the latest versions Guix knows about.

# check whether binary substitutions are available
$ guix weather  --manifest=./example1-profile.scm
computing 5 package derivations for x86_64-linux...
looking for 5 store items on https://ci.guix.gnu.org...
https://ci.guix.gnu.org ☀
100.0% substitutes available (5 out of 5)
at least 46.1 MiB of nars (compressed)
935.5 MiB on disk (uncompressed)
0.576 seconds per request (2.9 seconds in total)
1.7 requests per second

# pull down the updated definitions from the Guix repository
$ guix pull

$ guix package --profile=$GUIX_EXTRA_PROFILES/example1-profile/example1-profile \
        --manifest=./example1-manifest.scm

With the last command we're re-installing all the packages specified in the manifest, and as we've updated our definitions this will use any new versions.

Deleting a Profile

There's no built in way to delete a profile. But, as we stored all the definitions in a specific directory, by removing the directory we delete the profile.

$ rm -rf --interactive $GUIX_EXTRA_PROFILES/example1-profile/

$ guix package --list-profiles

Profile Resources

Profiles Summary

As we've seen profiles provide a way to logically separate a set of packages, and provide a method for activating the environment configuration needed to use them. When we use a manifest to create a profile, we can think of the profile as an instantiation of the packages defined in the manifest: so they're very complementary. Profiles can either be used in a standard shell environment or they can be used with the guix shell command to create temporary environments or containers.

In our next post we'll talk about creating a layered profile approach, how to manage multiple profiles and how to activate them at login.


Posted in Tech Wednesday 21 December 2022
Tagged with tech ubuntu guix