Skip to content

Instantly share code, notes, and snippets.

@noteed
Last active March 12, 2026 15:48
Show Gist options
  • Select an option

  • Save noteed/bbe689928cfa6ca1e55b7f2506e09626 to your computer and use it in GitHub Desktop.

Select an option

Save noteed/bbe689928cfa6ca1e55b7f2506e09626 to your computer and use it in GitHub Desktop.
Consume a devenv.lock file from default.nix

Consuming devenv.lock from default.nix (without flakes)

Clone this Gist:

git clone git@gist.github.com:/bbe689928cfa6ca1e55b7f2506e09626.git try-devenv-lock

This Gist demonstrates how devenv.lock can be parsed from plain Nix to resolve inputs without enabling flakes.

Files

File Purpose
devenv.yaml Standard devenv input declarations
devenv.nix Standard devenv configuration
devenv.lock Lock file generated by devenv update
default.nix uses devenv.lock for inputs pinning
shell-*.nix provides devenv to use the above

Build

We use one of the shell-*.nix file to provide devenv.

Then we can use devenv, nix build or nix-build to build our package:

$ nix-shell shell-devenv.nix 
$ devenv build outputs.app
✓ Building                                                                     6.2s
  └ ✓ Downloading  https://cache.nixos.org/nix-cache-info from cache.nixos.org 82ms
  └ ✓ Downloading  https://devenv.cachix.org/nix-cache-    from               186ms
  └ ✓ Evaluating Nix 1301 files                                               343ms
  └ ✓ Evaluating devenv.config.outputs.app 1299 files                         512ms
    └ ✓ Querying  app from cache.nixos.org                                    173ms
    └ ✓ Building  app                                                          33ms
{
  "outputs.app": "/nix/store/ybf4wyw9a7qv4nbwkp2miqi55i13rjx7-app"
}

$ nix build -f default.nix app --print-out-paths --no-link
/nix/store/ybf4wyw9a7qv4nbwkp2miqi55i13rjx7-app

$ nix-build -A app --no-out-link
/nix/store/ybf4wyw9a7qv4nbwkp2miqi55i13rjx7-app

Notes

We use shell-25.11.nix to get a "recent" version of devenv (1.11.1 in my case) and generate a devenv.lock file from the devenv.yaml file:

$ nix-shell shell-25.11.nix --run "devenv version"
devenv 1.11.1 (x86_64-linux)
$ nix-shell shell-25.11.nix --run "devenv update"

The generated lock file is just JSON, so we can use e.g. jq to inspect it:

$ jq '.nodes[.nodes.root.inputs.nixpkgs].locked' devenv.lock
$ jq '.nodes[.nodes.root.inputs.nixpkgs].locked | {owner, repo, rev}' devenv.lock

We can use the lock file to define a new Nix shell with pinned dependencies:

$ nix-shell shell-locked.nix --run 'devenv version'
devenv 1.11.2 (x86_64-linux)

To get a very recent devenv, we can use a GitHub release:

$ nix-shell shell-devenv-2.0.3.nix --run 'devenv version' \
  --option extra-substituters "https://devenv.cachix.org" \
  --option extra-trusted-public-keys \
    "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
devenv 2.0.3 (x86_64-linux)

The way we use the lock file in shell-locked.nix can also be used from a default.nix file. This should show the same ouput as the above jq call:

$ nix-instantiate --eval --strict --json \
  -A inputsMetadata.nixpkgs ./default.nix | jq '{owner, repo, rev}'
{ pkgs }:
pkgs.writeText "app" "Hello."
# Demonstrates parsing a devenv.lock file and resolving all GitHub-type inputs
# using builtins.fetchTarball.
#
# Usage:
# nix-instantiate --eval --strict --json -A inputsMetadata default.nix | jq .
# devenv build outputs.app
# nix build -f default.nix app --print-out-paths --no-link
# nix-build -A app --no-out-link
#
{ system ? builtins.currentSystem }:
let
lock = builtins.fromJSON (builtins.readFile ./devenv.lock);
nodes = lock.nodes;
# Fetch a GitHub-typed locked node as a source tree.
fetchLockedGithub = locked:
let
url = "https://github.com/${locked.owner}/${locked.repo}/archive/${locked.rev}.tar.gz";
in
if locked ? narHash
then builtins.fetchTarball { inherit url; sha256 = locked.narHash; }
else builtins.fetchTarball { inherit url; };
# Fetch any locked node, dispatching on type.
fetchLocked = node:
let
locked = node.locked;
in
if locked.type == "github" then
fetchLockedGithub locked
else
throw "Unsupported input type: ${locked.type}";
rootInputNames = builtins.attrNames nodes.root.inputs;
# Build a mapping of { inputName = { src, locked, original, ... }; }
#
# nodeKey can be a string (direct node reference) or a list (a "follows"
# path like ["git-hooks"] or ["devenv", "nixpkgs"]). We resolve the
# path by walking the nodes graph.
resolveNodeKey = key:
if builtins.isString key then
nodes.${key}
else if builtins.isList key then
# Walk the follows path: e.g. ["devenv" "nixpkgs"] means
# nodes.<nodes.root.inputs.devenv>.inputs.nixpkgs
let
walk = remaining: current:
if remaining == [] then current
else
let
nextKey = nodes.${current}.inputs.${builtins.head remaining};
in
if builtins.isString nextKey then
walk (builtins.tail remaining) nextKey
else
# nested follows
resolveNodeKey nextKey;
startKey = builtins.head key;
startNodeKey = nodes.root.inputs.${startKey};
in
if builtins.length key == 1 then
resolveNodeKey startNodeKey
else
walk (builtins.tail key) (
if builtins.isString startNodeKey then startNodeKey
else builtins.throw "Cannot resolve deeply nested follows from root"
)
else
builtins.throw "Unexpected node key type";
resolvedInputs = builtins.listToAttrs (map (name:
let
nodeKey = nodes.root.inputs.${name};
node = resolveNodeKey nodeKey;
in
{
inherit name;
value = {
src = fetchLocked node;
locked = node.locked;
original = node.original or {};
isFlake = !(node ? flake && node.flake == false);
};
}
) rootInputNames);
nixpkgsSrc = resolvedInputs.nixpkgs.src;
pkgs = import nixpkgsSrc {
inherit system;
config = {};
overlays = [];
};
# Show some metadata extracted from resolvedInputs
inputsMetadata = builtins.mapAttrs (name: input: {
type = input.locked.type;
owner = input.locked.owner or null;
repo = input.locked.repo or null;
rev = input.locked.rev or null;
narHash = input.locked.narHash or null;
lastModified = input.locked.lastModified or null;
url = input.original.url or
(if (input.original ? owner && input.original ? repo)
then "github:${input.original.owner}/${input.original.repo}"
+ (if input.original ? ref then "/${input.original.ref}" else "")
+ (if input.original ? dir then "?dir=${input.original.dir}" else "")
else null);
isFlake = input.isFlake;
}) resolvedInputs;
in
{
# Show some metadata extracted from resolvedInputs
# nix-instantiate --eval --strict --json -A inputsMetadata | jq .
inherit inputsMetadata;
app = import ./app.nix { inherit pkgs; };
hello = pkgs.hello;
}
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1773324704,
"narHash": "sha256-6zX24iDo+7ylxLGtFCi8aIFUaEzksdwHGm0lUewpb2A=",
"owner": "cachix",
"repo": "devenv",
"rev": "ed8175d32a5710b476be1a9afe22043d606b47be",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"nixpkgs": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1772749504,
"narHash": "sha256-eqtQIz0alxkQPym+Zh/33gdDjkkch9o6eHnMPnXFXN0=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "08543693199362c1fddb8f52126030d0d374ba2e",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1772173633,
"narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
{ pkgs, ... }:
{
outputs = {
app = import ./app.nix { inherit pkgs; };
hello = pkgs.hello;
};
enterShell = ''
echo "Hey."
'';
}
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
let
pkgs = import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz";
}) {
config = {};
overlays = [];
};
in
pkgs.mkShell {
packages = with pkgs; [
devenv
jq
git
];
}
let
pkgs = import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz";
}) {
config = {};
overlays = [];
};
devenvSrc = builtins.fetchTarball {
url = "https://github.com/cachix/devenv/tarball/v2.0.3";
};
devenvFlake = import devenvSrc;
system = builtins.currentSystem;
devenv = devenvFlake.packages.${system}.devenv;
in
pkgs.mkShell {
packages = [
devenv
pkgs.jq
pkgs.git
];
}
let
lock = builtins.fromJSON (builtins.readFile ./devenv.lock);
nodes = lock.nodes;
nixpkgsNode = nodes.${lock.nodes.root.inputs.nixpkgs};
url = "https://github.com/${nixpkgsNode.locked.owner}/${nixpkgsNode.locked.repo}/archive/${nixpkgsNode.locked.rev}.tar.gz";
nixpkgsSrc =
if nixpkgsNode.locked ? narHash
then builtins.fetchTarball { inherit url; sha256 = nixpkgsNode.locked.narHash; }
else builtins.fetchTarball { inherit url; };
pkgs = import nixpkgsSrc {
config = {};
overlays = [];
};
in
pkgs.mkShell {
packages = with pkgs; [
devenv
jq
git
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment