---
title: Lockfiles
toc_max_heading_level: 6
---

import VersionLabel from '@site/src/components/Docs/VersionLabel';

<VersionLabel version="0.51.0" header />

Lockfiles allow you to lock the exact version (and checksum) of every tool installed in a project,
ensuring that all machines — your own, your teammates', and CI — resolve and install identical
tools, regardless of when they run. If you've used package managers like npm, Cargo, or uv, the
concept is the same, just applied to the tools themselves.

:::caution

Lockfiles are currently unstable. The functionality works as documented, but behavior and the
lockfile structure may change between releases. If you run into any issues, please
[report them](https://github.com/moonrepo/proto/issues)!

:::

## Enabling lockfiles

Lockfiles are opt-in, and can be enabled with the
[`settings.unstable-lockfile`](./config#unstable-lockfile) setting in a `.prototools` file.

```toml title=".prototools"
node = "22"

[settings]
unstable-lockfile = true
```

Once enabled, a `.protolock` file will be created in the same directory as the `.prototools` file,
the next time records are written (typically by [`proto install`](./commands/install)). This file
should be committed to your version control system, so that other machines can make use of it.

If the setting is later disabled or removed, the sibling `.protolock` file will automatically be
deleted the next time proto runs.

## How it works

### Scope<VersionLabel version="0.60.0" />

A lockfile is scoped to the `.prototools` configuration file in which the setting was enabled.

- The lockfile only tracks tools with versions defined in its sibling `.prototools` file. Tools
  installed ad-hoc (without a configured version) are tracked by the closest configuration file.
- Versions defined in nested configuration files are not included in a parent's lockfile. To lock a
  nested configuration, enable the setting in that file as well.
- Multiple lockfiles can exist within a directory tree (for example, one per project in a
  monorepo), with each operating independently.

> Before v0.60, a lockfile locked its directory _and all of its subdirectories_, similar to how
> package manager workspaces function, and defining nested lockfiles would trigger an error.

### Records

For each tool, the lockfile stores a list of records, with each record representing an installation
for a specific version requirement, operating system, and architecture. Records are grouped by the
tool identifier using TOML's array of tables syntax.

```toml title=".protolock"
# Generated by proto. Do not modify!

[[tools.node]]
os = "linux"
arch = "x64"
spec = "22"
version = "22.17.0"
checksum = "sha256:325c377fbb4e547444a5da404e474fda52eb0adb3bbafcd25eee38e5766a1cfa"

[[tools.node]]
os = "macos"
arch = "arm64"
spec = "22"
version = "22.17.0"
checksum = "sha256:29b8fc16b1d1b0b7d19d444b8fac9e6ca4b39ee01bd097756975f92a25deba71"
```

Each record supports the following fields:

- `spec` - The [version requirement](./tool-spec#requirements-and-ranges) that was requested,
  typically from a `.prototools` file.
- `version` - The exact version that the requirement resolved to.
- `checksum` - A checksum of the installed artifact, prefixed with its algorithm. Builds from
  source and native installations may not have a checksum.
- `os` / `arch` - The operating system and architecture the record applies to. Since pre-built
  artifacts differ per platform, a record exists for each combination.
- `backend` - The [backend](./tool-spec#backends) the tool was installed with, if applicable.

Records are created and updated when a tool is explicitly installed with
[`proto install`](./commands/install), and removed when uninstalled with
[`proto uninstall`](./commands/uninstall). Do not edit this file manually — proto owns it entirely.

### Version resolution

When a version requirement is being resolved — during an install, or when detecting versions for
[`proto run`](./commands/run) and shims — proto will first look for a matching record in the
applicable lockfile. A record matches when it has the same requirement (`spec`), backend, operating
system, and architecture. If a match is found, the exact `version` from the record is used,
instead of re-resolving the requirement against the tool's available versions.

This is what guarantees determinism: a requirement like `node = "22"` will keep resolving to the
locked `22.17.0`, even after newer `22.x` versions have been released. To break out of this and
re-resolve, pass `--update-lockfile` to [`proto install`](./commands/install), which will skip the
lockfile during resolution, and update the record with the newly resolved version.

```shell
$ proto install node --update-lockfile
```

### Checksum verification

When installing a version that has a lockfile record, proto will verify the installation against
that record: the checksum of the artifact must match the locked `checksum`, and the operating
system and architecture must line up. If they don't, the install fails with an error — ensuring
that the artifact installed on another machine (or in CI) is the same one that was originally
locked.

When a record is migrated to a new version (through the commands below), its checksum is removed,
and will be re-populated on the next install.

## Lockfile-aware commands<VersionLabel version="0.60.0" />

Beyond installation, lockfiles are integrated across the command line.

- [`proto outdated`](./commands/outdated) and [`proto status`](./commands/status) display a
  "Locked" column, showing the version locked for each configured requirement, and include a
  `locked_version` field in their `--json` output.
- [`proto outdated --update`](./commands/outdated) updates matching records when writing new
  versions to their respective configuration files.
- [`proto pin`](./commands/pin) (and [`proto install --pin`](./commands/install)) migrates matching records to the newly
  pinned requirement, while [`proto unpin`](./commands/unpin) removes them entirely.
- [`proto versions`](./commands/versions) labels versions that have a record with "locked", and
  includes a `locked` field in its `--json` output.
- [`proto debug config`](./commands/debug/config) renders `.protolock` files alongside their
  sibling configuration files.
- [`proto clean`](./commands/clean) does _not_ remove records when cleaning stale versions, as the
  version may still be required by a configuration, or in use on other machines.
- [`proto diagnose`](./commands/diagnose) audits the health of lockfiles, reporting warnings for
  records that are missing a resolved version, duplicate records for the same requirement, and
  stale records that no longer match a configured version.
