Why I Ditched Docker and Put My AI Agent in a Real Mac
Docker is fine until your AI agent needs a real browser. Here is what broke, why a macOS VM fixed it, and what running near-native Apple Silicon virtualization actually looks like.
I want to tell you that my AI agent runs in a clean, minimal Docker container on a $5 VPS somewhere. That would make a better story. It would also be a lie.
Agu runs in a full macOS virtual machine on my M1 MacBook Air. The VM has 6GB of RAM, 50GB of disk on an external drive, a VNC password, a Tailscale address, and a LaunchAgent that auto-starts everything at boot. It is, by any reasonable measure, overkill.
It’s also the only setup that actually works.
What broke in Docker
I started with Docker because Docker is what you’re supposed to use. Containerised, reproducible, easy to move. The agent framework ran fine. The AI API calls worked. The Telegram integration worked.
The browser didn’t.
The specific failure was Chrome DevTools Protocol through Docker’s network bridge. CDP is how you control Chrome programmatically - navigate to pages, click things, fill forms, take screenshots. My agent needed to do all of that. And through Docker’s network bridge, the WebSocket connections kept dropping mid-session. No error. No timeout message. Just silence.
The first few times I thought it was my code. Then I thought it was the Chrome flags. Then I thought it was the proxy layer in front of Chrome. I spent more time debugging the container than building the actual agent. The container was the problem.
The underlying issue is that Docker’s network bridge adds a layer between the host and the container that the Chrome DevTools Protocol doesn’t handle gracefully. CDP is sensitive to connection quality in ways that most applications aren’t, because it’s maintaining a persistent WebSocket for the entire duration of a browser session. Any blip in that connection drops the session.
You can work around some of this with socat bridges and careful configuration. I did. It helped, then broke again in a different way.
At some point you’re spending more energy on the infrastructure than the actual work.
What lume is
lume is a macOS virtualization tool built on Apple’s own Virtualization.framework - the same technology that powers Apple Silicon virtualization at the OS level. You get a full macOS VM running on your Mac, at near-native performance.
Near-native is not a marketing claim in this context. The VM gets direct access to the CPU and memory without an emulation layer. On an M1, the VM runs macOS natively because the VM is running the same ARM architecture as the host. The overhead is real but small.
Creating a VM:
lume create agu-vm --os macos --ram 6144 --disk 50 \
--disk-path /Volumes/MacExtn/lume-vms/agu.lume
Once it’s booted, you SSH in. You set it up exactly like you would a real Mac - install your tools, configure LaunchAgents, set up your networking. Then you leave it running.
The Chrome problem that doesn’t go away
There’s one thing about Chrome that I learned the hard way and I haven’t seen written about clearly anywhere: Chrome always binds its DevTools Protocol to localhost, regardless of what flags you pass.
--remote-debugging-address=0.0.0.0 does nothing. This is true in headless mode, non-headless mode, containerised or not. Chrome’s security model doesn’t allow exposing CDP to external interfaces.
The fix is a TCP proxy that forwards external connections to localhost:
// Forward 0.0.0.0:9223 → 127.0.0.1:9222
const server = net.createServer(client => {
const target = net.connect(9222, '127.0.0.1');
client.pipe(target).pipe(client);
});
server.listen(9223, '0.0.0.0');
This runs as a LaunchAgent in the VM alongside Chrome. It’s two lines of code and it solves the problem permanently.
How I access it
The VM has a LAN IP on my home network and a Tailscale IP that works from anywhere. SSH access via Tailscale means I can manage the VM from my phone or from a work machine without thinking about it.
The agent’s control UI is exposed through an HTTPS Tailscale subdomain. There’s also a VNC password for when I need to look at what’s happening in the browser visually.
Day-to-day, I don’t think about the VM. It’s just there.
The honest tradeoffs
This setup is not for everyone.
The VM requires an always-on Mac. My laptop goes to sleep when I’m not at home, which means the agent isn’t accessible until I wake it. A VPS-based setup would be truly always-on.
Storage is the main real constraint. The VM takes 50GB on an external drive, plus the RAM split. On a 16GB M1 with the VM taking 6GB, the host has 10GB. That’s workable but tight if you’re running other demanding applications.
And it’s not exactly portable. If I want to move this setup to a different machine, I’m migrating a 50GB VM image plus reconfiguring everything. Not trivial.
For me, the tradeoffs are worth it. The browser automation works reliably. The performance is good. And there’s something to be said for running your AI agent on hardware you own, in a VM you control, with your data staying local.
What I’d tell someone starting this today
If your agent doesn’t need browser automation, Docker is fine. Run it on a $5 VPS and don’t complicate your life.
If your agent needs to control a real browser - fill forms, take screenshots of modern SPAs, interact with JavaScript-heavy apps - you’re going to hit the same CDP stability issues I hit. A macOS VM solves them. Linux with a virtual display might solve some of them.
The decision point is: do you want to spend your time debugging container networking, or do you want to just build the thing?
I chose the VM. I haven’t touched the infrastructure in weeks.
The agent framework running in this VM is OpenClaw. The Tailscale setup, LaunchAgent configs, and the TCP proxy for Chrome are all in my other posts.