One Mac, two users, one Homebrew
At some point you end up wanting two accounts on one Mac. Maybe the work you’re doing should live somewhere your personal browser sessions and SSH keys don’t. Maybe your employer wants a managed account and you’d rather not hand over the whole machine. Whatever the reason, the request is modest: one laptop, two logins, a clean line between them.
macOS handles the first ninety percent of that well. Separate home directories, separate
keychains, separate browser profiles, fast user switching. Then you open a terminal in the new
account and type brew, and nothing happens.
What follows is everything that broke, and why each thing broke, because the reasons turn out to be more interesting than the fixes.
The core problem: two different ideas of ownership
macOS is built around per-user isolation. Almost everything meaningful lives under
~/Library, and a second account genuinely starts from nothing.
Homebrew is built around a single shared prefix. On Apple Silicon that’s /opt/homebrew, and
it is owned by whichever user installed it. Not by root, not by a group, but by a person.
Those two models are individually sensible and jointly awkward. Your second account can see
/opt/homebrew because it’s world-readable, but it can’t write to it, and Homebrew wants to
write constantly: updating formulae, linking binaries, managing its own git repository.
There are three ways out and only one of them is good.
Install a second Homebrew. Works, and it’s the option Homebrew’s own documentation is most comfortable with. But you now have two package databases, two update cycles, and several gigabytes of duplicated Xcode-adjacent dependencies on a laptop SSD. You will also, eventually, be confused about which one you’re talking to.
Change ownership to a shared group. Tempting, and it appears in a lot of forum answers. It’s
fragile. Homebrew resets permissions during some operations, and you end up re-running chown
after upgrades and wondering why things broke.
Run brew as the user who owns it. This is the one I settled on:
alias brew='sudo -Hiu akc brew'
Where akc is my personal account, the one that installed Homebrew. The flags matter:
-u akcruns as that user-istarts a login shell, so Homebrew gets the environment it expects rather than an inherited one from the calling account-Hsets$HOMEto the target user’s home, which is what stops Homebrew writing its cache and logs into the wrong place
The result is that installing a package from the work account installs it into the personal account’s Homebrew, and both accounts use it.
That last part is the piece worth understanding. Homebrew’s management needs write access,
but the things it installs land in /opt/homebrew/bin, which is readable and executable by
everyone on the machine. So the second account doesn’t need Homebrew at all in order to use
what Homebrew installed. It just needs the path:
# ~/.zprofile
export PATH="$PATH:/opt/homebrew/bin"
.zprofile rather than .zshrc, because this is environment setup that should happen once at
login rather than on every interactive shell.
Docker, which fails differently
Docker Desktop breaks for a related but distinct reason, and the failure looks identical from
the terminal: docker: command not found.
The application itself is in /Applications and every account can launch it. But Docker
Desktop installs its CLI tools (docker, docker-compose, docker-buildx) into the user’s
home directory at ~/.docker/bin, and then symlinks or path-injects them per account. The
second user gets the app and none of the commands.
# ~/.zprofile
export PATH="$PATH:$HOME/.docker/bin"
Launch Docker Desktop once from the new account first, so it populates that directory. There’s a long-running thread on docker/for-mac about multi-user behaviour if you want the full history.
Dotfiles: symlink, don’t copy
The obvious move is to copy your config across. Don’t. You’ll edit one, forget the other, and spend a month wondering why your Neovim keybindings differ depending on which account you’re in.
Symlink instead, so there’s one source of truth. There are two places that source of truth can live, and the difference matters more than it first appears.
Option one: keep the repo in one user’s home and link across.
cd ~/.config
ln -s /Users/akc/.dotfiles/nvim/.config/nvim/ nvim
ln -s /Users/akc/.dotfiles/lazygit/.config/lazygit/ lazygit
This works, and it’s the smaller change if your dotfiles already live in ~. One caveat to
check on your own machine: macOS makes a home directory itself readable by other users but locks
down several subdirectories inside it. A .dotfiles repository sitting directly in ~ is
reachable; the same repo inside ~/Documents is not. If your symlinks resolve but the target
won’t open, that’s the reason, and moving the repo up a level fixes it.
Option two: move the repo to /Users/Shared and link from both accounts.
mv ~/.dotfiles /Users/Shared/dotfiles
cd ~/.config
ln -s /Users/Shared/dotfiles/nvim/.config/nvim/ nvim
ln -s /Users/Shared/dotfiles/lazygit/.config/lazygit/ lazygit
/Users/Shared exists on every Mac for exactly this: a directory outside anyone’s home that all
local accounts can read and write. This is what I ran for a long time, and it’s the better
default.
Two reasons. The permissions problem disappears, because you’re no longer reaching into another
account’s home directory and hoping macOS lets you. And it removes the asymmetry. With option
one there’s a primary account that owns the config and a secondary one that borrows it, so
deleting or renaming that account takes your dotfiles with it. In /Users/Shared neither
account is privileged, and both are just clients of the same repository.
The tradeoff is that /Users/Shared is writable by every local account, so anything with a
credential in it doesn’t belong there. Config yes, secrets no. That’s the rule anywhere, but
it’s easier to violate by accident when the directory is this convenient.
Shell config is the exception. .zshrc, .zprofile and .zshenv genuinely differ between the
accounts, with different aliases, different paths and different work-specific variables, so those get
copied once rather than linked, and then diverge on purpose.
Moving files between your own two accounts
This is more annoying than it should be, because from the filesystem’s point of view you are two unrelated people.
Every macOS account has a ~/Public folder that is readable by other users on the machine,
with a Drop Box inside it that is write-only from outside. That’s the intended channel, and
it’s what I use for the one-time transfers: shell config, zsh plugin directories
(zsh-autosuggestions, fast-syntax-highlighting), fonts.
Fonts are worth a note of their own. Installing to ~/Library/Fonts makes them available to
one account only; /Library/Fonts makes them available to everyone. If you’re using a patched
Nerd Font in your terminal, install it system-wide once and stop thinking about it.
For iTerm2, don’t hand-recreate your setup. Export the profile as JSON from the original
account, drop it in ~/Public, and import it in the new one.
What the setup actually looks like
After all of it, the division is:
Shared
- Homebrew and everything it installs
- System fonts
- Docker Desktop, the application
- A dotfiles repo in /Users/Shared, symlinked into ~/.config from both
Separate
- Shell configuration
- SSH keys
- Git identity
- Keychains and browser profiles
- Application state
- Docker's per-user CLI and contexts
Which is roughly the right line. The things that are expensive to duplicate are shared; the things that carry identity or credentials are not.
The thing I keep thinking about
None of these are bugs. Homebrew’s single-prefix design is a deliberate trade that makes the common case fast and simple, and macOS’s per-user isolation is correct. Docker Desktop putting its CLI in the user’s home is defensible too.
But every one of them encodes an assumption that a laptop belongs to one person, and that assumption is doing a lot of quiet work. The moment you need two accounts, whether for a job, a contract, or a boundary you want to keep, you’re not fighting a limitation so much as discovering a premise nobody wrote down.
There’s a version of this in most software I work on. The default configuration is the shape of someone’s assumption about who you are, and you only find out what that assumption was when you stop matching it.
Read next
- Twelve containers on a laptop I'd stopped usingWritingTwelve containers on a 2017 Dell Inspiron that had sat unopened for three years. What the path layout has to look like, why the auto-updater is commented out, and why the certificate setup I built first got thrown away.
- The app that would not let goWritingA relative's phone had an app on it that could not be uninstalled, could not be found by name, and blocked a factory reset. Finding it meant searching by what it could do rather than what it was called.