My MacBook Pro has an M2 Pro and 16 GB of RAM. For a while that was plenty. Then my normal day became: a few Next/Vite dev servers, Docker with Postgres and Redis, a couple of editors, a browser with too many tabs, and AI coding agents running in terminals next to all of it. Memory pressure stayed yellow, the fans came on for no reason, and the disk kept filling up with node_modules, Docker images and build caches.
The obvious fix was a new Mac with more memory. The honest problem: I can't afford one right now. A higher-spec MacBook costs a lot of money up front. A dedicated server costs a monthly fee I can actually fit into a budget.
So I tried something else: keep the Mac, but stop asking it to do the heavy work.
The setup
The server: a Hetzner AX41 from their server auction / dedicated line.
- AMD Ryzen 5 3600 (6 cores, 12 threads)
- 64 GB RAM
- 2 × 512 GB NVMe in RAID 1
- Ubuntu 26.04
- Costs me €57
The Mac: the same 16 GB M2 Pro, now mostly running a terminal, a browser and an editor.
Everything that eats memory or disk lives on the server now: repos, dependencies, Docker, dev servers, databases and the agents.
How it's wired together
Tailscale for everything private
The server is on my tailnet as hause-dev. The firewall (ufw) denies everything incoming by default. The only public ports are 80 and 443 for Caddy. SSH, mosh, Samba and my other dev services only accept connections over the tailscale0 interface.
That means:
-
ssh hauseworks from my Mac, and nothing on the public internet can even try it. - If I start a dev server with
--host(or-H 0.0.0.0for Next), I openhttp://hause-dev:3002on my Mac and it just works. The rest of the internet can't see it. - If a server only binds to localhost, an SSH tunnel does the job:
ssh -N -L 3002:localhost:3002 hause.
Docker runs remotely, but feels local
I don't run Docker on the Mac anymore. The Docker CLI on my Mac uses a context that points at the server over SSH:
docker context create hause --docker "host=ssh://hause" docker context use hause docker ps, docker compose up and so on all run on the server. For a GUI I use Gantry, a native macOS Docker app, pointed at the same SSH host. My Mac no longer carries a Linux VM eating several GB of RAM.
The server's files show up in Finder
Sometimes I just want to drag a file in or open an image without thinking about scp. So the server shares my code folder over Samba (SMB), the same protocol macOS uses for network drives. Samba only listens on the Tailscale interface, so the share isn't reachable from anywhere else.
On the Mac it's built in: in Finder, press ⌘K, enter smb://hause-dev, and save the password to Keychain. The server then shows up under Locations in the sidebar like any other drive. I added it to Login Items so it reconnects when I log in.
It's not for heavy work. Running npm install or git over a network share at 135 ms would be miserable, so those still happen over SSH on the server itself. The share is for browsing, previewing and dragging files around.
Moving years of repos without losing anything
This was the scariest part. I had a lot of projects in ~/code, some with uncommitted work, gitignored .env files, and branches that only existed locally.
Instead of trusting rsync and hoping, I:
- Copied every repo across, including the gitignored files that mattered.
- Hashed every file on both machines and compared the trees.
- Found commits that existed on the Mac but not on the server, and imported them (there were 35 I would have lost).
- Archived old projects I don't touch anymore into
~/archiveon the server instead of deleting them.
Only after everything matched did I start cleaning up the Mac.
What I deliberately did not move
Credentials. Cloud logins, API keys, SSH private keys, AWS and Kubernetes configs, CLI auth tokens: none of it got copied. Every tool on the server got logged into fresh. It's slower, but it means the server only has the access I deliberately gave it, and I know exactly what that is.
Shell and tools
The server got the same zsh setup, CLI tools and dotfiles as my Mac, so SSHing in feels like home. My server's SSH key got added to the other machines I manage, so I can hop from the server to them just like I used to from the Mac.
su.hause.dev: a tiny admin panel with passkeys
Once all my dev servers lived somewhere I couldn't see, I wanted a quick way to check on them from any device. So I built su.hause.dev: a small Go binary that shows the server's live process list, memory and CPU, and lets me quit runaway processes.
Repo: https://github.com/dilutedev/hause.dev
Some decisions I'm happy with:
- Passkeys only. No passwords. The first device signs in through a one-time setup link printed to the server logs. New devices choose "Request access", show a code, and an already-signed-in device approves it.
- Recent passkey check for anything dangerous. Quitting a process or adding and removing devices needs a passkey check from the last 10 minutes.
- It can't kill what matters. init, systemd services, sshd, tailscaled, Caddy, Docker and the panel itself are shown, but can't be stopped from the UI.
- Least privilege. It runs as its own system user whose only sudo right is
/usr/bin/kill. - Everything is logged. Every sign-in, device change and signal goes to the systemd journal.
The same repo also holds the hause.dev landing page and the deploy scripts (systemd unit, sudoers rule, Caddy site, install script).
The results
- The Mac feels new again.
- Disk space: free space on the Mac went from 126 GB to 271 GB after moving repos, Docker and old caches off it.
- No more choosing between apps. 64 GB means I can run all my projects' dev servers, databases and agents at once without closing things.
The trade-offs (it's not perfect)
- Latency. My round trip to the server is about 135 ms. Plain SSH typing feels it. mosh helps a lot, and editors with remote support (that run the language server on the server) hide most of it.
- No internet, no dev environment. On a plane or bad Wi‑Fi, I'm stuck with whatever is still on the Mac.
- A Ryzen 5 3600 isn't an M-series chip. Single-core speed is slower than my M2 Pro. I'm trading speed per core for lots of memory and cores that are never fighting my browser.
- Monthly cost. Over enough years, a server costs as much as a new laptop would.
- Native Mac/iOS work still needs a Mac. Xcode builds don't move to Linux.
If you can afford a MacBook with 32 or 64 GB of RAM and you want everything local and offline, that's probably the better choice. This is the right choice for me right now, because it turned a big expense I couldn't make into a small one I can.
Was it worth it?
Yes, and it was genuinely fun! :)
I ended up with a faster Mac, a server I understand top to bottom, and a little panel on my phone that tells me what's eating my RAM.
If you're stuck on an underpowered laptop and can't upgrade yet, a rented box plus Tailscale might be enough to make it feel new.