Using devenv with flake-parts
Flake Parts provides a modular framework for organizing Nix Flakes. This integration helps you structure your devenv configuration within a Nix Flake.
Before proceeding, we recommend reading about the benefits and downsides of using Nix Flakes as this approach requires familiarity with Nix Flakes concepts.
Getting started
Section titled “Getting started”To quickly set up project with Nix flakes, use nix flake init:
nix flake init --template github:cachix/devenv#flake-partsThis will create a flake.nix file with a basic devenv configuration and a .envrc file for direnv support.
Working with flake shells
Section titled “Working with flake shells”The flake.nix file
Section titled “The flake.nix file”Here’s an example of a minimal flake.nix file that includes devenv:
{ inputs = { flake-parts.url = "github:hercules-ci/flake-parts"; devenv.url = "github:cachix/devenv"; nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling"; };
outputs = inputs@{ flake-parts, nixpkgs, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.devenv.flakeModule ]; systems = nixpkgs.lib.systems.flakeExposed;
perSystem = { config, self', inputs', pkgs, system, ... }: { # Per-system attributes can be defined here. The self' and inputs' # module parameters provide easy access to attributes of the same # system.
# Equivalent to inputs'.nixpkgs.legacyPackages.hello; packages.default = pkgs.hello;
devenv.shells.default = { # https://devenv.sh/reference/options/ packages = [ config.packages.default ];
enterShell = '' hello ''; }; }; };}Here a single shell is defined for all listed systems.
The shell includes a single devenv configuration module, under devenv.shells, named default.
Add your devenv configuration (usually in the devenv.nix file) to this module.
See devenv.nix options for more information about configuration options.
Entering the shell
Section titled “Entering the shell”Enter the devenv shell using:
nix develop --no-pure-evalThis will create a lock file and open a new shell using the devenv configuration from your flake.nix.
Launching processes, services, and tests
Section titled “Launching processes, services, and tests”Once in the shell, you can launch processes and services with devenv up.
$ devenv up17:34:37 system | run.1 started (pid=1046939)17:34:37 run.1 | Hello, world!17:34:37 system | run.1 stopped (rc=0)And run tests with devenv test.
$ devenv testRunning tasks devenv:enterShellSucceeded devenv:git-hooks:install 10msSucceeded devenv:enterShell 4ms2 Succeeded 14.75ms• Testing ...Running tasks devenv:enterTestSucceeded devenv:git-hooks:run 474msNo command devenv:enterTest1 Skipped, 1 Succeeded 474.62msImport a devenv module
Section titled “Import a devenv module”You can import a devenv configuration or module, such as devenv-foo.nix into an individual shell as follows.
Add imports to your devenv.shells.<name> definition:
# inside perSystem = { ... }: {
devenv.shells.default = { imports = [ ./devenv-foo.nix ];
enterShell = '' hello '';};You can use definitions from your flake in your devenv configuration.
When you do so it’s recommended to use a different file name than devenv.nix, because it may not be standalone capable.
For example, if devenv-foo.nix declares a devenv service, and you’ve packaged it locally into perSystem.packages, you can provide the package as follows:
# inside perSystem = { config, ... }: {
devenv.shells.default = { imports = [ ./devenv-foo.nix ];
services.foo.package = config.packages.foo;
enterShell = '' hello '';};Your devenv module then doesn’t have to provide a default:
{ config, lib, ... }:let cfg = config.services.foo;in { options = { services.foo = { package = lib.mkOption { type = lib.types.package; defaultText = lib.literalMD "defined internally"; description = "The foo package to use."; }; # ... }; }; config = lib.mkIf cfg.enable { processes.foo.exec = "${cfg.package}/bin/foo"; };}Automated shell switching with direnv
Section titled “Automated shell switching with direnv”Activate your shell automatically when you enter the project directory.
```consolecurl -o .envrc https://raw.githubusercontent.com/cachix/devenv/main/templates/flake-parts/.envrc```
```consoledirenv allow```Caching devenv up with direnv
Section titled “Caching devenv up with direnv”By default, devenv up re-evaluates the flake before starting processes to pick up any changes.
With direnv, the shell is reloaded automatically whenever the flake changes, keeping the environment up to date.
Under direnv, devenv up, devenv test, and devenv tasks skip re-evaluation and use the cached environment directly, starting significantly faster.
Multiple shells
Section titled “Multiple shells”Some projects lend themselves to defining multiple development shells. For instance, you may want to define multiple development shells for different subprojects in a monorepo.
You can do this by defining the various development shells in a central flake.nix file in the root of the repository.
The flake.nix file outputs multiple devShells when you provide multiple perSystem.devenv.shells definitions.
For example:
# inside perSystem = { ... }: {
devenv.shells.projectA = { # https://devenv.sh/reference/options/ packages = [ config.packages.default ];
enterShell = '' echo this is project A hello '';};
devenv.shells.projectB = { # https://devenv.sh/reference/options/ packages = [ config.packages.default ];
enterShell = '' echo this is project B hello '';};
# If you'd like to pick a defaultdevShells.default = config.devShells.projectA;Here we have defined two shells, each with a devenv configuration and differently defined enterShell command.
To enter the shell of projectA:
$ nix develop --no-pure-eval .#projectAthis is project A(devenv) $To enter the shell of projectB:
$ nix develop --no-pure-eval .#projectBthis is project B(devenv) $The last line makes projectA the default shell:
$ nix develop --no-pure-eval .this is project A(devenv) $External flakes
Section titled “External flakes”If you cannot, or don’t want to, add a flake.nix file to your project’s repository, you can use external flakes instead.
Create a separate repository with a flake.nix file, as in the example above. Then refer to this flake in your project:
$ nix develop --no-pure-eval file:/path/to/central/flake#projectAthis is project A(devenv) $You can also add this to the direnv configuration of the project. Make sure the following line is in .envrc:
nix flake --no-pure-eval file:/path/to/central/flake#projectAExternal flakes aren’t limited to local paths using file:. You can refer to flakes on github: and generic git: repositories.
See Nix flake references for more options.
When using this method to refer to external flakes, it’s important to remember that there is no lock file, so there is no certainty about which version of the flake is used. A local project flake file will give you more control over which version of the flake is used.

