Photo by Jose Vazquez on Unsplash
Why you should flox every day
(I hope I will be forgiven for this very questionable pun ๐)
I want to share a very valuable tool that I have been using for almost a year now: flox
.
How I avoided nix for so long
I never had a strong desire to completely control the software running on my machine. I was more focused on setting up an environment quickly and then dedicating all my time to developing software. However, this approach came at a cost. Every time I switched companies, I had to set up a brand-new computer and reinstall all my software. Typically, this meant a few days of frustration, but it also allowed me to clear out all the accumulated clutter from my previous job.
I was envious though of those people who were re-installing their computer in a matter of minutes, dot files, system libraries, development environments, etc...
Some of them had a secret trick for this: Nix.
Nix should have been appealing to me as a functional programmer, given its features like immutable deployments, isolation, easy rollbacks, and more. However, Nix had a reputation for being difficult to understand, challenging to use, and constantly evolving.
Ian Henry's "How to Learn Nix" is an excellent rendition of some of those difficulties. I could feel in my bones the kind of rabbit hole that learning Nix would be. Is it worth the loss of a few days every few years?
If I'm being honest, it was more than that. In my previous job, we had a mixture of technologies: Haskell, Go, Python, Postgres, etc. Even using some cryptographic libraries in Python meant building Rust code! As a result, my development environment started breaking much more frequently. Every month or so, some system library would be missing, python
wouldn't be the right version anymore, and my PATH
would become a mess. Nix was becoming increasingly compelling...
It doesn't have to be hard
With flox
I can create several environments:
dotfiles
for myzsh
configurationdefault
for my common day-to-day toolsscala
for my open-source Scala projectshaskell
for my open-source Haskell projectsrust
for my open-source Rust projectsockam
for my project at work
My .zshrc
configuration file now is simply:
eval "$(flox activate -e flox/default)"
eval "$(flox activate -e dotfiles)"
This starts the flox/default
environment giving me access to the latest version of flox
, then my dotfiles
environment.
The activate
command:
grabs the packages defined in the environment from a nix packages repository, builds them, and caches them locally (so that it is only slow the first time)
puts the binaries included in those packages on the
PATH
Dot files
What is in my dotfiles
environment? We can have a look at it with the edit
command:
> flox edit -e dotfiles
{
shell.hook = ''
export ZDOTDIR=$HOME/.dotfiles
exec zsh --no-globalrcs
'';
packages.nixpkgs-flox.zsh = {};
packages.nixpkgs-flox.starship = {};
}
The dotfiles
environment exports the ZDOTDIR
variable and starts zsh
.
The ZDOTDIR
variable points to $HOME/.dotfiles
directory, which is versioned with Github. That directory contains all the files used in my .zsh
configuration.
I am also fetching the fabulous starship
prompt, which, with a bit of configuration, allows me to display the currently activated flox
environments
I now have:
versioned dot files,
which can be easily reinstalled if I use a new computer
The many tools of the trade
Wait! Where does the additional default
environment come from? This environment is activated by the $HOME/.dotfiles/.zshrc
configuration file. It contains all the tools I rely on to be effective at my job. For example the ultra-fast versions of grep
and find
(rg
and fd
, implemented in Rust).
flox list -e default
provides the whole list:
Several things to mention about this list:
this is the 101st generation! I have frequently modified this list.
flox
can actually list all the modifications since it was created. I can also revert to any previous version if I want toall the usual suspects can be found in that list:
git
,rg
,tree
, etc... There is a very high chance that if you need an executable, you will find it by searching the nix packagessome packages come from my own repositories. I published what was missing, using, ...
flox
Publishing the rest
For example, the excellent cmt
tool allows you to use a template to format your commit messages (respecting the conventionalcommits standard if you want to)
This is a tool that I use every day but it does not exist as a nix
package. So I published it myself!
I forked the original repository and ran flox init
to make it a flox project:
There is no pre-packaged project template for Haskell (which is how cmt
is implemented), so I used package-simple
.
This is where I had to learn more about Nix, so I could understand how to build the project. cmt
is a small Haskell project so building it is not particularly involved
โฏ cat pkgs/commit-tool/default.nix dotfiles flox/default default
{ self, pkgs, stdenv, lib, haskellPackages }:
with haskellPackages;
mkDerivation rec {
pname = "cmt";
version = "0.7.1.1";
src = ../../.;
isExecutable = true;
libraryHaskellDepends = [
ansi-terminal attoparsec base classy-prelude containers directory
file-embed filepath process terminal-size text
];
executableHaskellDepends = [ base classy-prelude ];
doHaddock = false;
doCheck = false;
homepage = "https://github.com/smallhadroncollider/cmt#readme";
description = "Write consistent git commit messages";
license = lib.licenses.bsd3.spdxId;
mainProgram = "cmt";
}
The definition above declares that:
we want to make an executable named
cmt
sources can be found at
../../.
the package depends on other Haskell libraries
The next step is to use flox publish
to make the package available to everyone. My teammates can subscribe to the repository containing the package and install it in their environment.
But they don't even have to do that!
Sharing is caring
One great benefit of creating a flox
environment is the ability to save it to a Github repository with the flox push
command.
This is useful for yourself because, the day you switch to another laptop, you can simply download the environment again with flox pull
and be ready in minutes.
This is also particularly useful for teams working on the same project because you can make sure that everyone is always working with the same version of the same tools: postgres
, python
, jq
, etc... thus reducing the risk of "But it works on my machine!".
Another significant benefit of using environments is their complete isolation. Activating an environment only adds the executables for that specific environment to the PATH
. When you exit the environment, everything is removed. You have complete control over what is in scope.
Note: I have been using flox
mostly as a package manager to handle "managed environments". There is also a concept of "project environments" where the flox
configuration contains everything: how to build, test, release. The command used to enter a project environment is just flox develop
in the project directory.
Less typing
One last tip. Having to type flox activate -e scala
every time you want to work on a Scala project can become pretty tiresome. The solution to this problem is to use direnv
. With direnv
you add a .envrc
file to your project directory:
eval "$(flox activate -e scala)"
Every time you cd
into the project directory, direnv
runs the .envrc
file and that activates the right environment. Neat!
Getting some help
I encourage you to explore Flox and see how it can assist you in managing all your environments effortlessly. However, when you venture into building and publishing packages you might have to better understand flox
's notion of catalog/channels and know your way around nix
. Don't hesitate to leave a message on the flox Discourse site, the team is very responsive and friendly!