Webinar: Managing Macs with Intune: security, scale, & the modern enterprise fleet
Register to attend
Tutorial

Create a private Tap with a custom formula and cask

Petros Amoiridis

In this tutorial we will create a private Homebrew Tap repository, add a formula and a cask to it, connect it to Workbrew, and confirm that both packages appear in the Workbrew Console. Along the way we will learn how Workbrew discovers Taps and what repository structure it expects.

Before we start, we need a GitHub organization with the Workbrew GitHub App already installed and at least one managed Device in our Workspace. If the GitHub App is not installed yet, follow Sync and authenticate private Taps with Workbrew first, then come back here.

Create the repository

First, we create a new private repository in our GitHub organization. We name it homebrew-internal. The homebrew- prefix is how Workbrew identifies Tap repositories, so the name matters.

Now we clone it and set up the directory structure:

git clone git@github.com:acme-corp/homebrew-internal.git
cd homebrew-internal
mkdir Formula Casks

Let's confirm the structure looks right:

ls -1

We should see:

Casks
Formula

Notice the capitalization: Formula is singular with a capital F, and Casks is plural with a capital C. Workbrew only looks in directories with these exact names.

Add a formula

We create a file called hello-internal.rb inside the Formula directory. This formula defines a simple internal tool:

class HelloInternal < Formula
  desc "A simple greeting script from a private tap"
  homepage "https://github.com/acme-corp/homebrew-internal"
  url "https://github.com/acme-corp/hello-internal/releases/download/v1.0.0/hello-internal-1.0.0.tar.gz"
  sha256 "e3b0c44298fc1c149afb4c8996fb92427ae41e4649b934ca495991b7852b855"
  version "1.0.0"

  def install
    bin.install "hello-internal" => "hello-internal"
  end
end

The url and sha256 point to where the source archive is hosted. For now, we are focused on getting the Tap structure right. We will come back to hosting the actual archive later.

Add a cask

Next, we create a file called example-app.rb inside the Casks directory. This cask defines an internal macOS application:

cask "example-app" do
  version "1.0.0"
  sha256 "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"

  url "https://github.com/acme-corp/example-app/releases/download/v1.0.0/ExampleApp-1.0.0.dmg"
  name "Example App"
  desc "An internal macOS application"
  homepage "https://github.com/acme-corp/example-app"

  app "Example App.app"
end

Let's check our directory tree one more time before we commit:

find . -not -path './.git/*' -not -path './.git' | sort

We should see:

.
./Casks
./Casks/example-app.rb
./Formula
./Formula/hello-internal.rb

This is exactly what Workbrew expects. Files must be inside Formula/ or Casks/ to be discovered. Anything in the repository root is ignored.

Push to the default branch

We commit and push our work:

git add Formula/hello-internal.rb Casks/example-app.rb
git commit -m "Add hello-internal formula and example-app cask"
git push origin main

Enable the Tap in Workbrew

Now we switch to the Workbrew Console. We open Workspace settings and navigate to the GitHub Installations tab.

Our homebrew-internal repository should appear in the list of available Taps. We check the box next to it to enable it, then click Sync Now.

After a moment, the sync completes. We can confirm it worked by checking the Tap's detail page in the Console. We should see both hello-internal listed as a formula and example-app listed as a cask.

What we have built

We now have a private Homebrew Tap that Workbrew has discovered and synced. The formula and cask definitions are visible in the Console and available to managed Devices in our Workspace.

If the Tap appears but no packages are listed, we should check that the formula and cask files are valid Ruby that Homebrew can parse, that they are inside Formula/ and Casks/, and that they were pushed to the default branch.

To make the formula and cask installable on Devices, their url fields must point to archives that Devices can download. See the Private Taps reference for details on URL accessibility and other constraints. For more on the formula and cask DSL, see Homebrew's How to Create and Maintain a Tap.