How to Build a Mac-Within-a-Mac Toolbox?

A persistent Linux box on your Mac for installing things you do not entirely trust, running services that start at login, and keeping the mess out of your real machine.

Setup time ~25 minutes
Disk budget ~15 GB (cap at 20 GB)
One-time cost Free
Going cost Free

Ingredients

Why a toolbox?

Every developer’s Mac slowly turns into a museum: a half-finished Postgres install from a project two years ago, a Homebrew tap that won’t upgrade, three Pythons fighting over /usr/local, an npm CLI from a Hacker News thread that probably did something on its way through your home directory.

A toolbox is a Linux machine on your Mac with three rules:

Concretely, this is where you should be installing:

This recipe is a different shape from the per-workload Apple container approach: there each task gets its own micro-VM that dies when it exits; here one Linux box lives across reboots, holds state, and runs services. Different problem, different tool. Both are useful; you may well end up with both installed.

1 Install OrbStack

brew install orbstack

Open the OrbStack app once so it can install its virtualisation helpers. When the first-run wizard asks what you want, pick Linux machines (the container side is not needed for this recipe; you can turn it on later).

2 Cap resources

In OrbStack → Settings → System:

3 Create the toolbox

orb create ubuntu:24.04 toolbox

Drop into it:

orb shell -m toolbox

You are now inside a fresh Ubuntu 24.04 install with systemd running as PID 1, your Mac username as the default user, and passwordless sudo. This is the playground.

4 Pick exactly one shared folder

By default OrbStack auto-mounts your entire Mac home directory at /mnt/mac inside the VM. For a toolbox that is the opposite of what you want — the whole point is that things installed in here cannot see ~/Documents, ~/Downloads, or your SSH keys.

Turn the broad mount off in Settings → File Sharing (uncheck “Mount user home directory”), and add one explicit mapping:

mkdir -p ~/toolbox-share

From inside the VM, verify:

ls -la /share
touch /share/hello.txt
# the file appears on the Mac immediately

5 Install things without fear

Inside the toolbox, install whatever you like. A reasonable starter kit:

sudo apt update
sudo apt install -y build-essential curl git tmux htop jq \
    sqlite3 ripgrep fd-find unzip

# Your own Python
curl https://pyenv.run | bash
# Your own Node
curl -fsSL https://fnm.vercel.app/install | bash
# Linuxbrew, if you want a second package manager
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

None of this touches your Mac. pip install --user very-suspicious-package, npm i -g something-shady, curl … | sudo bash — whatever happens happens inside this 20 GB box and nowhere else.

Network reach: the toolbox has full outbound internet but cannot reach files on your Mac unless they are in ~/toolbox-share. That is usually what you want. If you also want network isolation, add an iptables egress rule inside the VM — OrbStack does not police outbound traffic for you.

6 Run services inside the toolbox

Ubuntu 24.04 in OrbStack runs real systemd. Anything you install that ships a system service can be enabled the normal way:

sudo apt install -y postgresql
sudo systemctl enable --now postgresql
sudo systemctl status postgresql

For your own apps, write a tiny user service so you do not need sudo for it:

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/my-app.service <<'EOF'
[Unit]
Description=My App
After=network-online.target

[Service]
ExecStart=/home/me/.local/bin/my-app --port 8080
Restart=on-failure
RestartSec=3

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable --now my-app

# so user services start at VM boot, not at login
sudo loginctl enable-linger $USER

That last command — loginctl enable-linger — is the one most tutorials skip. Without it your user services only run while you have an active shell in the VM, and die the moment the SSH connection drops.

7 Reach the services from the Mac

OrbStack auto-publishes anything the VM listens on. If my-app binds to port 8080 inside the toolbox, on the Mac you can hit it at:

# by machine name (preferred — no port conflicts)
curl http://toolbox.orb.local:8080/

# or by localhost, if no Mac app is using that port
curl http://localhost:8080/

Mac apps see these as ordinary network services — your browser, psql, a desktop database GUI, all work without configuration. Conversely, services on the Mac are reachable from inside the VM at host.orb.internal.

8 Start at login

Two switches, both off by default:

  1. OrbStack itself starts at login. Open OrbStack → Settings → General, and turn on “Start at login”.
  2. The toolbox machine starts when OrbStack starts. On the Linux machines page, right-click toolbox“Start on launch”.

That is enough for normal use: you log in, OrbStack comes up, the toolbox boots, systemd inside it brings up Postgres and your services, and ports are immediately reachable from Mac apps.

9 Belt-and-braces with launchd

If the toolbox is hosting something you really do not want to be down — say a sync service or a self-hosted dashboard you keep open all day — back the OrbStack auto-start up with a launchd agent. Even if OrbStack crashes or quits, this will bring it (and the machine) back.

Save as ~/Library/LaunchAgents/dev.howto.toolbox.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>dev.howto.toolbox</string>

    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/orb</string>
        <string>start</string>
        <string>toolbox</string>
    </array>

    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
    </dict>

    <key>ThrottleInterval</key>
    <integer>30</integer>

    <key>StandardOutPath</key>
    <string>/tmp/orb-toolbox.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/orb-toolbox.err</string>
</dict>
</plist>

Load it:

launchctl load -w ~/Library/LaunchAgents/dev.howto.toolbox.plist

Now launchd will start the toolbox at login and restart orb start toolbox within 30 seconds if it ever exits non-zero. To disable:

launchctl unload -w ~/Library/LaunchAgents/dev.howto.toolbox.plist
Path note: the plist hard-codes /opt/homebrew/bin/orb, the Apple Silicon Homebrew location. On Intel Macs change it to /usr/local/bin/orb. Run which orb if unsure.

Backups

The toolbox is supposed to be disposable, but the data in ~/toolbox-share is on your Mac and gets swept up by Time Machine like anything else — no extra work. For VM-internal state worth keeping (databases, dotfiles), the cheapest backup is to tar them into the shared folder on a cron:

# inside the toolbox
crontab -e

# nightly snapshot of Postgres to the Mac side
0 3 * * * /usr/bin/pg_dumpall -U postgres | gzip > /share/backups/pg-$(date +\%F).sql.gz

Burn it down, rebuild in a minute

Anything in here gone wrong? The reset is unceremonious:

orb delete toolbox
orb create ubuntu:24.04 toolbox

~/toolbox-share on the Mac is not touched. If you anticipate doing this often, keep a small bootstrap script in the shared folder (/share/bootstrap.sh) that re-installs your preferred set of tools — one command and you are back.

What you end up with

A real Linux machine on your Mac, capped at 4 cores / 4 GB / 20 GB, that boots when you log in, brings its services up via systemd, and is reachable from Mac apps on toolbox.orb.local. The only path between it and your real machine is one folder. Anything you install in here — trustworthy or not — cannot escape the box, and if it ever misbehaves you delete the whole thing and recreate it in under a minute.

Other runtimes that fit the shape

OrbStack is the smoothest path, but the same idea works with other VM runtimes. The trade-offs are mostly about ergonomics:

Further reading