You wrote a small CLI tool — a deploy script, a log parser, a backup wrapper. Two coworkers want to use it on their Macs. You email them a tarball. Three months later you fix a bug. Now you have to remember whose machines have it, who’s on the old version, and how to redeploy. By the time the team is five people you’ve reinvented half of apt.
The right answer for “I have a tool, and a few Macs need to install it” is a Homebrew tap. It costs you a single GitHub repo and ten lines of Ruby. Your team installs your tool with one command, gets updates with brew upgrade, and you get a real version-history. Here’s the smallest possible setup.
What a tap actually is
A “tap” is a public or private GitHub repo named homebrew-<something> that contains formulas: small Ruby files describing how to download and install a piece of software. When someone runs brew tap your-org/your-tap, brew clones that repo into $(brew --repository)/Library/Taps/your-org/homebrew-your-tap. From then on, every formula in the repo behaves exactly like a formula from the official Homebrew core — brew install, brew upgrade, brew uninstall all work.
The repo is just a folder of Ruby files. You don’t need to write an installer, you don’t need to maintain a signing pipeline, you don’t need to host binaries on your own server (though you can). For most internal tools, the formula points at a tagged GitHub release tarball.
Step 1 — make your tool installable
Your CLI tool needs:
- A GitHub repo (public or private — both work).
- A tagged release. Even
v0.1.0on a single commit is fine. - A way to install itself when someone unpacks the tarball — a
Makefilewith amake installtarget, or a bin/ directory with the executable already at the right path. For a single shell script, justchmod +x bin/mytool.
For example, if your tool is a single Bash script:
# my-tool/Makefile
PREFIX ?= /usr/local
install:
install -m 0755 bin/mytool $(PREFIX)/bin/mytoolThat’s all the install logic you need. Tag the repo:
git tag v0.1.0 && git push --tagsStep 2 — create the tap repo
The repo MUST be named homebrew-<name>. Convention: name it after your team or your product line, not after the tool. So if your team is “platform” and you’ll publish 3 tools, the tap is your-org/homebrew-platform.
gh repo create your-org/homebrew-platform --public
git clone https://github.com/your-org/homebrew-platform.git
cd homebrew-platform
mkdir FormulaStep 3 — the formula
Create Formula/mytool.rb:
class Mytool < Formula
desc "One-line description of what mytool does"
homepage "https://github.com/your-org/my-tool"
url "https://github.com/your-org/my-tool/archive/refs/tags/v0.1.0.tar.gz"
sha256 "REPLACE_ME_AFTER_FIRST_DOWNLOAD"
license "MIT"
def install
system "make", "install", "PREFIX=#{prefix}"
end
test do
assert_match "mytool 0.1.0", shell_output("#{bin}/mytool --version")
end
endTo get the SHA256 for the url, run:
curl -sL "https://github.com/your-org/my-tool/archive/refs/tags/v0.1.0.tar.gz" | shasum -a 256Paste that hash into the formula. Commit and push the tap repo. That’s it — your tap is live.
Step 4 — your team installs it
brew tap your-org/platform
brew install mytool
# from now on, this just works
mytool --version
brew upgrade mytool # when you tag v0.2.0If your tool is in a private repo, brew can clone it via SSH if your team has GitHub keys set up:
brew tap your-org/platform git@github.com:your-org/homebrew-platform.gitReleasing a new version
- Tag a new release of your tool repo:
git tag v0.2.0 && git push --tags. GitHub creates the tarball automatically. - Recompute the SHA256 of the new tarball.
- Edit the formula in the tap repo: bump the version in the
urlline and update thesha256. - Commit + push the tap repo.
- Done.
brew upgrade mytoolwill pick up the new version on every team Mac.
You can automate steps 2–4 with a GitHub Action triggered on the tool repo’s release event — but for a small team, doing it by hand the first few times is fine.
What about Linux?
Homebrew on Linux is a real thing — the same tap works on both macOS and Linux installs of brew. You don’t need to do anything special unless your tool has macOS-specific dependencies (in which case add an on_macos do block to the formula).
Why this beats writing your own installer
- You inherit version pinning, dependency declarations, audit logs, and uninstall. brew tracks all of it.
- Your users don’t memorize per-tool install commands. They learned
brew installonce. - You get free Apple Silicon + Intel + Linux support as long as your tool is portable. brew’s bottle system can even cache pre-compiled artifacts if your tool has a real build step.
- It scales to the team’s whole tool catalog without becoming the work of one dedicated SRE. The marginal cost of formula #2 is two minutes.
