# Pinning

Pinning keeps your developer environment reproducible. Each [input](/inputs/) in `devenv.yaml` is resolved to an exact revision and stored in `devenv.lock` — the same idea as a lockfile in other ecosystems (package-lock, poetry.lock, composer.lock, and so on).

## How it works

You declare inputs in `devenv.yaml`:

**devenv.yaml**

```yaml
inputs:
  nixpkgs:
    url: github:cachix/devenv-nixpkgs/rolling
```

devenv writes the resolved revisions into `devenv.lock`. After that, everyone who uses the same lockfile gets the same dependency versions — until someone runs `devenv update`.

You do not need a separate “create lock” step. The lock is created or updated when you use devenv on the project.

## Viewing pins

```sh
$ cat devenv.lock
```

`devenv info` also shows locked inputs.

## Pinning an individual package version

Pinning a nixpkgs input selects one revision of the whole package collection. To select an exact version of an individual package instead, add the [nixpkgs-multiverse](https://github.com/fzakaria/nixpkgs-multiverse) input:

```shellsession
$ devenv inputs add nixpkgs-multiverse github:fzakaria/nixpkgs-multiverse
```

Then select packages by attribute and version in `devenv.nix`:

**devenv.nix**

```nix
{ multiverse, ... }:


{
  packages = [
    multiverse.cmake."3.16.5"
    multiverse.bun."0.7.0"
  ];
}
```

`devenv.lock` pins the Multiverse revision, and its version index selects the corresponding historical nixpkgs revision for each package. Those historical revisions are fetched only when referenced. Running `devenv update nixpkgs-multiverse` intentionally refreshes the version index; until then, the selected packages remain reproducible. See [Installing a specific version](/packages/#installing-a-specific-version) for usage details and limitations.

Each pin resolves independently, and multiple pins may require multiple nixpkgs revisions to be fetched and evaluated, adding overhead. `multiverse.pins` resolves the entire set using the fewest revisions possible.

**devenv.nix**

```nix
{ multiverse, ... }:


{
  packages = multiverse.pins {
    cmake = "3.26.4";
    bun = "0.7.0";
  };
}
```

See [Pinning several packages](/packages/#pinning-several-packages) for what minimizing decides, and [the fewest nixpkgs](https://fzakaria.com/2026/08/17/nixpkgs-multiverse-the-fewest-nixpkgs) for why it is minimal.

## Pinning a nixpkgs revision

Use `?rev=` in the input URL for an exact commit:

**devenv.yaml**

```yaml
inputs:
  nixpkgs-stable:
    url: github:NixOS/nixpkgs/ac62194c3917d5f474c1a844b6fd6da2db95077d
```

Use `?ref=` for a branch or tag (for example the NixOS 25.05 release branch):

**devenv.yaml**

```yaml
inputs:
  nixpkgs-stable:
    url: github:NixOS/nixpkgs?ref=nixos-25.05
```

With `?rev=…`, that commit is fixed in the declaration. With only a branch or tag (`?ref=…` or a path like `…/nixos-25.05`), the lockfile freezes whatever commit was current when the lock was last written; it does not keep moving on every command.

## Updating pins

`devenv update` is how you **intentionally refresh** the lockfile — same role as `npm update`, `poetry update`, or similar. Day-to-day commands keep using the revisions already in `devenv.lock`. They do not pull newer commits from a floating branch just because upstream moved.

Refresh every input’s locked revision (where the URL still points at a branch or tag):

```sh
$ devenv update
```

Refresh a single input by name:

```sh
$ devenv update nixpkgs-stable
```

That re-resolves the input from its URL and rewrites `devenv.lock` if the result changed. Commit the new lock so the team and CI pick up the same pins.

## Adding inputs from the CLI

```sh
$ devenv inputs add nixpkgs-stable github:NixOS/nixpkgs/nixos-25.05
$ devenv inputs add my-input github:org/repo --follows nixpkgs
```

This updates `devenv.yaml`. The lock is refreshed the next time you run a devenv command on the project.

## Commit the lockfile

Commit `devenv.lock` to version control. It is what makes the environment reproducible across machines.

If the lockfile is missing, devenv creates one by resolving inputs. Without a shared lock, floating branches or tags can resolve to different commits on different machines.