Skip to content

Project environments

You can use flox to set up a development environment for an existing project or when creating a project from scratch. Let's see how this can be done with a few commands:

Note

It was previously recommended to use a flox.nix file to define a project environment. Using a flox.nix file is still supported, but it currently has a number of limitations for development (for example it doesn't set variables like PKG_CONFIG_PATH and PYTHONPATH). Although we fully intend to overcome those limitations in the future, in the mean time we'll show you how you can get work done today using default.nix.

Initialize a project

Let's setup the initial of a project called example-project using the flox init command:

$ mkdir example-project && cd example-project
$ flox init --template project # (1)!
> The current directory is not in a Git repository, would you like to create one in "/home/USER/example-project"? Yes
Created git repo: /home/USER/example-project
wrote: /home/USER/example-project/flake.nix
> Enter package name example-project
wrote: /home/USER/example-project/shells/__PACKAGE_NAME__/default.nix
wrote: /home/USER/example-project/shells/__PACKAGE_NAME__
wrote: /home/USER/example-project/shells
[INFO] [flox_rust_sdk::models::project] moved: /home/USER/example-project/shells/__PACKAGE_NAME__ -> /home/USER/example-project/shells/example-project
Run 'flox develop' to enter the project environment.
  1. flox init can be used to jumpstart your project with some initial structure for your project.

    See more details in the man page.

Let's look closer at the files that were generated:

shells/example-project/default.nix

Project environments should be defined in a shells/<project name>/default.nix file.

default.nix
{
  gnumake,
  mkShell,
  stdenv,
  zlib, # (1)!
}:
#
# Create a development shell using three sections:
# `packages`, `buildInputs`, and `shellHook`.
#
mkShell { # (2)!
  # Compilers and libraries go here
  buildInputs = [
    stdenv.cc
    zlib
  ];

  # Add extra tools here
  packages = [
    gnumake
  ];

  # Any variable set in this block that isn't a reserved word will be set as an
  # environment variable in the environment.
  WELCOME_MESSAGE = "Run make to build this project";

  # A shell hook is a script to run when entering an environment.
  # It can be used to perform any custom activation steps needed for your
  # project.
  shellHook = ''
    make --version
    echo
    echo "$WELCOME_MESSAGE"
  '';
}
  1. The packages in this block are made available in the block below, so listing packages here can be seen as importing them.
  2. mkShell is a helper function from Nixpkgs for creating developer environments.

flake.nix - a file that provides compatibility with Nix.

flake.nix is created when you initialize an environment. This is created in the root directory of your project.

flake.nix
{
  description = "A flox project";
  # (1)!

  inputs.flox-floxpkgs.url = "github:flox/floxpkgs";
  # (2)!

  outputs = args @ {flox-floxpkgs, ...}: flox-floxpkgs.project args (_: {});
  # (3)!
}
  1. (optional) A flake description of the project.

  2. flox-floxpkgs is a place where all flox-related packages, modules and libraries are located.

  3. flox-floxpkgs.project is a helper function which will scan your project for flox environments.

Add packages to an environment

A typical workflow to add packages to a development environment would be:

Note

We use flox nix search rather than flox search below because flox search only searches packages from the catalog and does not include certain Nix helper functions and packages from Nixpkgs. We intend to fix this deficiency of flox search in the future.

$ flox nix search nixpkgs openssl
...
* legacyPackages.aarch64-darwin.openssl (3.0.8)
  A cryptographic library that implements the SSL and TLS protocols
...

$ # add openssl to default.nix

$ git diff shells
diff --git a/shells/example-project/default.nix b/shells/example-project/default.nix
index c4a4549..aa663aa 100644
--- a/shells/example-project/default.nix
+++ b/shells/example-project/default.nix
@@ -1,6 +1,7 @@
 {
   gnumake,
   mkShell,
+  openssl,
   stdenv,
   zlib,
 }:
@@ -11,6 +12,7 @@
 mkShell {
   # Compilers and libraries go here
   buildInputs = [
+    openssl
     stdenv.cc
     zlib
   ];

Enter the environment

Installing a package to an environment does not make it instantly available. For it to be available for use we need to activate the environment with the flox develop command. In this example, you can validate that your openssl package was added to the environment by running which openssl.

Note

Project environments are entered with flox develop while other flox environments are entered with flox activate. flox develop performs setup needed for development, for example it adds paths of libraries to relevant environment variables, in addition to adding binaries to PATH. flox activate, on the other hand, sets PATH but does not set paths to libraries, so it is useful for creating environments with development tools (but not libraries) or binaries ready for production. Environments used by flox activate are managed imperatively with the flox CLI or declaratively in a flox.nix file.

$ make --version
GNU Make 3.81  # (1)!
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0

$ flox develop  # (2)!
GNU Make 4.4.1
Built for aarch64-apple-darwin22.3.0
Copyright (C) 1988-2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Run make to build this project

$ which make  # (4)!
/nix/store/a51vzhr9hdfyyhp5m803y3k4mp9na19f-gnumake-4.4.1/bin/make

$ git add flake.lock # (5)!

$ exit  # (6)!
  1. Although make is present, it's a very old version from 2006.

  2. We enter the project environment with flox develop.

  3. The output from running shellHook is displayed here.

  4. Inside the project environment we now have a newer version of the make binary which was provided by the gnumake package.

  5. flake.lock is the lock file of the project and locks all dependencies used by the project to a specific version. If you wish to reproduce your environment on another machine or share it reproducibly with somebody else, you should add it alongside your project code.

  6. When we are done playing or working inside the environment we should not forget to exit it.

Behind the scenes the following file was created:

  • flake.lock: stores the exact version of flake inputs (dependencies in Nix terminology). This is similar to how Cargo.lock locks all Rust crate dependencies.

Adding platform specific dependencies

A macOS or Linux specific dependency can be added as follows:

$ git diff shells
$ git diff shells
diff --git a/shells/example-project/default.nix b/shells/example-project/default.nix
index c4a4549..31b52bf 100644
--- a/shells/example-project/default.nix
+++ b/shells/example-project/default.nix
@@ -1,4 +1,7 @@
 {
+  hostPlatform, # (1)!
+  lib, # (2)!
+  libiconv, # (3)!
   gnumake,
   mkShell,
   openssl,
@@ -11,11 +14,15 @@
 #
 mkShell {
   # Compilers and libraries go here
-  buildInputs = [
-    openssl
-    stdenv.cc
-    zlib
-  ];
+  buildInputs =
+    [
+      openssl
+      stdenv.cc
+      zlib
+    ]
+    ++ lib.optional hostPlatform.isDarwin [ # (4)!
+      libiconv
+    ];

   # Add extra tools here
   packages = [

  1. hostPlatform provides constants about the platform building this environment.
  2. lib contains helper functions for writing Nix expressions.
  3. libiconv is the actual dependency we want to add to the environment.
  4. Use hostPlatform.isLinux to add a Linux specific dependency.

Sharing the environment

Assuming all Nix files created above are commited, including flake.lock, anyone can use the environment. Regardless of whether they're using Linux or macOS, they can simply clone the git repo and run:

flox develop

Where to next?

  • To automatically activate an environment, read flox integrates with direnv which does an amazing job at activating environment when switching to a folder.