← Writing

How I turned a laptop into a production server

Small side projects don’t need a cloud bill. They need a computer that’s on. Full case on /work#aurion and more broadly in the projects page.

I had a laptop doing nothing, a home internet connection with a dynamic IP, and a pile of services I wanted running somewhere. Below is what I ended up with, and what surprised me along the way.

The stack

Three pieces do most of the work:

That’s the whole ingress story. Public traffic enters through Cloudflare; admin traffic enters through Tailscale; the laptop’s firewall denies everything else.

What I thought would break, but didn’t

Power events. I expected this to be the messy part. In practice, systemd plus Docker’s restart: unless-stopped handled it fine. The laptop recovers from a power cut within a minute of coming back, and Cloudflare Tunnel reconnects on its own.

The laptop’s battery. I assumed I’d have to disable charging to avoid wearing it out. I left it alone for six months; it’s fine. Modern laptop batteries throttle themselves at 100% plugged-in better than desktops-with-batteries from a decade ago did.

My ISP. Turns out residential internet with a tunnel is remarkably stable if you don’t need inbound ports. The failure mode isn’t “packets dropped”; it’s “the whole link is down,” which is pretty binary to detect and monitor.

What actually needed work

Backups. The laptop is a single point of failure for any data living on it, so I run:

# nightly, via systemd timer
restic backup /var/lib/docker/volumes \
  --tag nightly \
  --exclude-file /etc/restic-exclude

Restic to a cheap object storage bucket. The restore path is tested end-to-end — if you’ve never actually restored a backup, you don’t have backups.

The other thing is observability. On one host you don’t need a full metrics stack, but you do need to know when a container has been restart-looping for three hours. I run a tiny uptime checker that pings the services through their public URLs and alerts me on WhatsApp if anything fails twice in a row.

What it costs

Electricity. That’s the entire operating expense. The domain is separate, and Cloudflare Tunnel is free at my traffic levels.

For comparison, the same services on a small managed PaaS would run somewhere in the $40–$120/month range depending on the tier, before egress.

What I’d do differently

Two things.

First, I’d put more thought into the hostname scheme earlier. I mixed .internal Tailscale names with Cloudflare public hostnames inconsistently for the first few services, then had to clean it up.

Second, I’d adopt Coolify’s “application stack” concept from the start rather than deploying things as individual containers. The abstraction costs nothing once you’re in it, and unwinding the ad-hoc approach later is more work than just starting there.

Is this for everyone?

No. If your side project serves real user traffic or you care about five-nines, go buy managed hosting. The point isn’t that this is better — it’s that for hobby and early-stage work, the cost curve of cloud PaaS is wrong, and this solves the specific problem of “I want to run ten small things without thinking about a bill.”

If you’re the same kind of person who’d find that tradeoff interesting, this setup is probably worth an afternoon.

← All writing Book a call →
Book a call → WhatsApp