Patterns for devenv configuration — language setup, services, tooling (git-hooks, scripts, processes), outputs, and profiles. For Nix language syntax, flake structure, or general NixOS module patterns, see nix-ecosystem instead; this skill covers only what is devenv-specific.
devenv 2.0
Released March 2026. Two changes affect existing configs directly:
-
The native process manager is now the default, replacing process-compose. A config that assumes process-compose behavior (health checks,
depends_on, readiness probes) silently gets the native manager instead unlessprocess.manager.implementation = "process-compose";is set explicitly — there is no error, just different runtime behavior fordevenv up. To keep process-compose:process.manager.implementation = "process-compose";
processes.api = { exec = "cargo run"; process-compose.readiness_probe.http_get = { host = "localhost"; port = 8080; path = "/health"; }; };
- devenv 0.x is deprecated; support drops in devenv 3.
Other 2.0 changes are cosmetic to the config author: a live TUI on every command, background rebuilds in the
native shell (save a file, `Ctrl+Alt+R` applies the new environment without leaving the shell), a C FFI
backend replacing spawned `nix` subprocesses, an incremental per-attribute eval cache, and cross-repo output
references.
## devenv vs. flakes
Use devenv over pure flakes when you need `services.*` (simpler than hand-rolled service derivations),
git-hooks integration, process supervision, or `languages.*` version management. Use flakes directly for pure
derivations or NixOS system configuration — devenv adds overhead there.
## Multi-shell and custom outputs (2.0+)
`devenv.shells.<name>` defines multiple shell configurations in one file — each is a full devenv module, not
an overlay of the default:
```nix
{ pkgs, ... }:
{
devenv.shells.default = {
languages.python.enable = true;
packages = [ pkgs.git ];
};
devenv.shells.ci = {
languages.python.enable = true;
env.CI = "true";
};
}
devenv.outputs exposes arbitrary flake outputs (packages, checks) from within the module:
{ pkgs, config, ... }:
{
devenv.outputs = {
packages.my-app = config.languages.rust.import ./. {};
checks.lint = pkgs.runCommand "lint" {} ''
${pkgs.clippy}/bin/cargo-clippy
touch $out
'';
};
}
Language modules expose an import builder for packaging an app with the same toolchain the shell uses —
config.languages.rust.import ./rust-app {} — instead of hand-writing a stdenv.mkDerivation.
Profiles (1.9+)
Three distinct mechanisms, easy to conflate:
- Value override —
profiles."python-3.12".config = { languages.python.version = "3.12"; };, selected withdevenv shell --profile python-3.12. - Composable modules —
profiles.backend.module = { ... },profiles.frontend.module = { ... }, thenprofiles.fullstack.extends = [ "backend" "frontend" ];to combine them. - Automatic selection by environment —
profiles.hostname."ci-server".moduleandprofiles.user."developer".moduleapply based on the machine's hostname or the invoking user, with no explicit--profileflag needed. This is a silent-selection mechanism: a shell picks up the profile because of who or where it's running, not because of anything in the invocation — worth checking for before debugging "works on my machine" config drift.
git-hooks
git-hooks.hooks wires pre-commit hooks (formatters: nixfmt-rfc-style, prettier, black, rustfmt,
ormolu, gofmt; linters: eslint, clippy, shellcheck, yamllint, actionlint, hadolint,
markdownlint; security/hygiene: detect-private-keys, check-merge-conflicts, check-case-conflicts,
editorconfig-checker; treefmt as a universal formatter wrapper). Custom hooks take an entry shell
command and a files regex:
git-hooks.hooks.my-custom-hook = {
enable = true;
entry = "${pkgs.bash}/bin/bash -c 'echo Running custom hook'";
files = "\\.nix$";
pass_filenames = true;
};
Scripts, tasks, processes
scripts.<name>.exec puts a command on $PATH inside the shell; tasks.<name> adds dependency ordering via
before/after, including hooking into shell entry itself (before = [ "devenv:enterShell" ];) — a task can
run automatically on every devenv shell invocation this way, which is easy to miss when reading a config
that otherwise looks static.
processes.<name>.exec are managed by whichever process manager is active (native by default in 2.0, see
above). Keep enterShell fast and offline-capable — it runs on every shell entry — and push network-dependent
or slow setup into a task with an explicit trigger instead.
Environment and secrets
dotenv.enable = true; (with dotenv.filename to pick a non-default file) loads .env-style files that
should stay out of version control. Don't put real secrets in the env attribute directly — it lands in the
Nix store, which is world-readable on the local machine.
Context7
Library id /cachix/devenv (trust score 9.7, ~1354 snippets) — use for current languages.*/services.*
option shapes, available git-hooks, and the devenv 2.0 migration guide, since option surfaces here change
between minor versions faster than this file is updated.
Related
- nix-ecosystem — Nix language syntax, flake structure and inputs, general NixOS
module patterns,
lib.*functions; devenv builds on all of it but doesn't re-teach it. - serena-usage — symbol operations for navigating devenv configurations.
- context7-usage — fetching current devenv documentation via Context7 MCP.