Webinar: Homebrew for Regulated Industries
Register to attend
Guide

Workbrew Lock (beta)

Luke Hefson

The missing piece for guaranteed, version-locked Homebrew installs across teams, projects, and platforms. If you’ve ever experienced a broken build because someone ran brew upgrade or tried to replicate an environment across macOS, Linux and WSL, you know the pain. Homebrew's principle of rolling releases makes it almost impossible to stay on one version. Workbrew Lock fixes that so you can focus on coding, not configuration.

TL;DR example

Workbrew Lock TL;DR animation

To have NodeJS versioned through Workbrew Lock and Git run:

# First, install and setup Workbrew on the Device
# Second, create HOMEBREW_WORKBREW_LOCKFILE=1 Brew Configuration in the Workbrew Console
$ brew bundle add node
$ brew bundle install
$ git add Brewfile Brewfile.workbrew.lock.json
$ git commit -m "Add Brewfile and Workbrew Lock"
$ brew bundle exec -- which node

Anyone running brew bundle install with Workbrew will get the same brew bundle exec -- which node output, reflecting the NodeJS version in the Git-tracked lock file.

Features

Workbrew Lock provides:

  • Versioned formulae lock files (Brewfile.workbrew.lock.json) to guarantee everyone on your team/project is running the same versions.

  • A brew bundle wrapper that uses these versions intelligently to manage your environment, shell and services with these versioned tools.

  • Acts as a replacement for or integrates with language version managers such as rbenv, nodenv and pyenv.

  • Parallelised/faster downloads and checks for brew bundle and Brewfiles through our Go-based Workbrew Agent.

  • Defaults for brew bundle based on the above i.e. no versions are upgraded without explicit request, locked versions are not cleaned up.

New Issues/Feedback

Please contact us through the details on the Contact page.

In this beta, the older your locked versions are, the more likely you are to encounter issues.

If you're blocked, brew bundle upgrade fixes all known issues.

Basic Usage

If you've never heard of brew bundle or Brewfiles before: check out the Homebrew brew bundle and Brewfile documentation.

Assuming you've read (or skimmed) most of that, here's the next steps:

  1. Install Workbrew on your device (if you haven't already)

  2. Ensure which brew in your shell/terminal outputs /opt/workbrew/bin/brew

  3. Set export HOMEBREW_WORKBREW_LOCKFILE=1 in your shell environment (e.g. add it to your .zshrc or .bash_profile) or create a Brew Configuration in the Workbrew Console with the key HOMEBREW_WORKBREW_LOCKFILE and value 1 to enable use on all Devices in your Workspace

  4. Enter a directory containing a Brewfile or create a Brewfile in the current directory

  5. Run brew bundle. If ==> Using Brewfile.workbrew.lock.json is outputted: things are working as expected.

  6. Note the Brewfile.workbrew.lock.json file that was created. We recommend committing it to version control for use by your team.

You can then use brew bundle check and brew bundle install in your scripts and CI workflows.

Advanced Usage

Workbrew Lock really shines when used with the commands brew bundle exec, brew bundle sh and brew bundle env.

brew bundle exec

As in Homebrew Bundle, brew bundle exec will run a command in an environment customised by your Brewfile.

Unlike Homebrew Bundle, Workbrew Lock will also use versions.

For example, with a Brewfile like:

brew "node", version_file: ".node-version"

And a Brewfile.workbrew.lock.json like:

{
  "formulae": [
    {
      "name": "node",
      "version": "23.11.0"
    }
  ]
}

This will ensure you are always running the specific version of node:

$ brew bundle exec which node
==> Using Brewfile.workbrew.lock.json
/opt/homebrew/Cellar/node/23.11.0/bin/node

Regardless of how your team's environments are configured, regardless of running brew upgrade node in Homebrew: this version will be fixed until you run brew bundle upgrade or brew bundle --upgrade-formulae node

Using brew bundle exec --services will also start any services in your Brewfile at the locked version. This is particularly useful for e.g. MySQL, PostgreSQL, etc. where it's useful to match your local development database version with the production version.

brew bundle sh

As in Homebrew Bundle, brew bundle sh will create a new interactive shell (or "virtual environment") with the environment setup.

Unlike Homebrew Bundle, Workbrew Lock will configure the environment with all tools at the same specific versions.

Using brew bundle sh --services will also start your shell with any services in your Brewfile at the locked version.

brew bundle env

As in Homebrew Bundle, brew bundle env dumps out all the environment variables in a form suitable for adding to a shell.

Unlike Homebrew bundle, Workbrew Lock will include environment variables for specific versions of tools and cache this data for speed. A .Brewfile.workbrew.lock.cache.json is written which allows future invocations of brew bundle env to take 0.01s. You should add .Brewfile.workbrew.lock.cache.json to gitignore as it may include device-specific output.

This enables brew bundle env with Workbrew Lock to be used with direnv in .envrc files:

layout_workbrew_lock() {
  export HOMEBREW_WORKBREW_LOCKFILE=1
  brew bundle check >/dev/null && eval "$(brew bundle env)"
  watch_file .Brewfile.workbrew.lock.cache.json
}

layout workbrew_lock

This means changing to a new directory will automatically setup the Homebrew environment.

Disabling Workbrew Lock

By default, Workbrew Lock will attempt to write a Brewfile.workbrew.lock.json for every Brewfile with formulae.

If you wish to disable this you can:

  • add # workbrew-lock:disable to the Brewfile

or:

  • create a .Brewfile.workbrew.lock.disabled file in the same directory as the Brewfile