Skip to content

Instantly share code, notes, and snippets.

Last active March 11, 2024 13:11
Show Gist options
  • Save lheckemann/402e61e8e53f136f239ecd8c17ab1deb to your computer and use it in GitHub Desktop.
Save lheckemann/402e61e8e53f136f239ecd8c17ab1deb to your computer and use it in GitHub Desktop.
Expression for a buildEnv-based declarative user environment.

Expression for a buildEnv-based declarative user environment

This is one way of managing your user profile declaratively.

Alternatives include:

  • an attrset-based nix-env-based environment, installed using nix-env -ir rather than nix-env --set. LnL has an overlay which shows a way of doing this.
  • home-manager, which provides NixOS-like config for your $HOME

Note that this is incompatible with regular imperative use of nix-env, e.g. nix-env -iA nixpkgs.hello. It has the advantage of allowing the installation of multiple outputs of the same package much better than nix-env's builtin profile builder does.

I personally currently use home-manager.

Feature comparison:

Prevents imperative use of nix-env (nix-env -iA nixpkgs.hello)

  • buildEnv ✔️
    • buildEnv takes responsibility for the entire user profile, meaning that nix's builtin env builder cannot modify it
  • attrset ❌
    • the attrset approach leaves building the profile up to nix-env, which allows adding packages ad-hoc (though they will be removed on the next profile build) using nix-env -i
  • home-manager ❌
    • home-manager installs a single buildEnv into the user profile, remaining compatible with imperative/impure nix-env

Management of files outside ~/.nix-profile

  • buildEnv ❌
  • attrset ❌
  • home-manager ✔️

Declarative config like NixOS

  • buildEnv ❌
  • attrset ❌
  • home-manager ✔️

Gives good control over outputs installed

  • buildEnv ✔️
  • attrset ❌
  • home-manager ❓
Save this file to ~/default.nix or your preferred path (make sure to
change the location in the update-profile script if you choose a
different place).
Install using `nix-env -f ~ --set`, from then on use `update-profile`.
{ pkgs ? import <nixpkgs> {}
, name ? "user-env"
}: with pkgs;
buildEnv {
inherit name;
extraOutputsToInstall = ["out" "bin" "lib"];
paths = [
nix # If not on NixOS, this is important!
(writeScriptBin "update-profile" ''
nix-env --set -f ~ --argstr name "$(whoami)-user-env-$(date -I)"
# Manifest to make sure imperative nix-env doesn't work (otherwise it will overwrite the profile, removing all packages other than the newly-installed one).
(writeTextFile {
name = "break-nix-env-manifest";
destination = "/manifest.nix";
text = ''
throw ''\''
Your user environment is a buildEnv which is incompatible with
nix-env's built-in env builder. Edit your home expression and run
update-profile instead!
# To allow easily seeing which nixpkgs version the profile was built from, place the version string in ~/.nix-profile/nixpkgs-version
(writeTextFile {
name = "nixpkgs-version";
destination = "/nixpkgs-version";
text = lib.version;
Copy link

This gist helped me a lot for build my setup and I will share my config for hopefully helping others.

# flake.nix

  description = "my-user-env";

  # inputs are your dependencies
  inputs = {
    nixpkgs.url = "nixpkgs/nixos-unstable"; # where we get the packages
    flake-utils.url = "github:numtide/flake-utils"; # a small utility that helps not hardcode the system architecture. Something like 'x86_64-linux'

  # Here we pass the input to outputs
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system};
      rec {
        # Here we are importing a file in the same directory of the flake.nix, named 'profile.nix'
        packages.${system}.user-env = import (./profile.nix) { pkgs = nixpkgs.legacyPackages.${system}; };
        defaultPackage = packages.${system}.user-env;

You have to change <path-to-flake> to the location you want

# profile.nix

{ pkgs ? import <nixpkgs> { }, name ? "user-env" }: with pkgs;

buildEnv {
  inherit name;
  # to get all the symlink we need
  extraOutputsToInstall = [ "out" "man" "lib" ];
  paths = [
    # ... put your packages here
    # this will create a script that will rebuild and upgrade your setup. Is using shell script syntax
    (writeScriptBin "nix-rebuild" ''
      cd <path-to-flake> || exit 1
      nix flake update
      nix profile upgrade '.*'
    # puts in your root the nixpkgs version
    (writeTextFile {
      name = "nixpkgs-version";
      destination = "/nixpkgs-version";
      text = lib.version;


To install your packages you only need to cding in the directory containing the flake and run nix profile install . and run nix-rebuild when you want to update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment