Managing background processes with homebrew in MacOS Ventura and beyond

Aji Slater

My homebrew installation of Postgres is always there when I need it. 😍 I can be confident that even just after login, my database is only a psql away.

Homebrew 🍺 (everyone’s favorite missing package manager for MacOS) not only manages installation and cleanup of the stuff we need that Apple doesn’t provide, but provides an abstraction over managing background processes with the native daemon manager launchctl.

What’s launchctl and why should I care?

Launchctl is Apple’s replacement for cron. Cron was deprecated in OSX Tiger, version 10.4. (That’s 2005 for those of you keeping score).

cron wasn’t the only tool replaced but alsoxinetd, mach_init and init. Launchctl was created to serve as a single performant native API to replace those UNIX tools.

Launchctl can handle running programs at certain times, like cron, but also in response to other triggers like login or file changes, and can relaunch programs if they crash.

What does homebrew give me that using launchctl directly doesn’t?

Starting a process on login without brew services means writing (or copying, or symlinking) a plist file to ~/Library/LaunchAgents for a single user and /Library/LaunchDaemons for all users.

The plist, which stands for “property list”, acts as instructions to launchctl about how, when, and what to do with the application.

Here is a snippet of a plist from my homebrew managed Postgres installation:


If the app was updated or uninstalled, choosing to manage the file means remembering to update or delete the plist ourselves.

brew services handles all that for us.

When we run brew services start postgresql@14 we’re telling homebrew to copy the necessary plist to the correct folder and manage it. If we remove postgres (😱), homebrew removes the plist.

Something changed in MacOS Ventura?

Apple made a change in MacOS 13 that provides a new scheme for these launchctl plist files.

“Rather than apps installing their own helpers and property lists, MacOS 13 (Ventura) introduces a new scheme where those are provided within the app” – The Eclectic Light Company

It should make this process more seamless for users, but not every program will or has taken advantage of this yet, so brew services isn’t going away.

Something also changed in homebrew?

That’s right! Previously we would need to “tap” (install) the services package before using it with brew tap homebrew/services, but it will now install itself the first time we run the command.

Because services is the preferred way to manage launch items installed via homebrew, messaging after installing applications no longer directs us to symlink plist files to */Library, but instead to use brew services.

What do I need to know?

There are only a few commands commonly needed to manage loading processes with homebrew.

brew services start xyz and brew services stop xyz will start and stop an application, but will also remove or add the plist file instructing launchctl to run the program at login.

If a running application needs to be turned off and on again (as we all do sometimes), brew services restart xyz will do that for us.

That’s usually the extent of it, but also handy are these—

brew services run xyz and brew services kill xyz will start and stop an application without changing anything with regards to launchctl.

Should we want to see what processes are running via launchctl, some of which might not be managed by homebrew, we can run brew services list. This is what mine currently looks like–

postgresql@14 started aji  ~/Library/LaunchAgents/homebrew.mxcl.postgresql@14.plist
redis         started aji  ~/Library/LaunchAgents/homebrew.mxcl.redis.plist

Can I silence that warning that pops up every time I change something with brew services?

Sadly, no.

Apple wants us to know what is happening in the background of our computer, and rightly so! Security is always in tension with convenience, and in this tradeoff, our notification centers will have to pay the price.