My Laptop Is Never Idle, So I Rented One That Is
Moving cold Rust builds from a busy laptop to a rented Hetzner box: what it costs, and how long the round trip takes.
Here’s uptime on my laptop, on an ordinary evening:
$ uptime
00:43:55 up 7 days, 8:58, 1 user, load average: 4.54, 5.82, 8.75
Load of 8.75 over the last fifteen minutes, on twelve threads. That’s a guix build compiling a GTK app with -C lto -C codegen-units=1, a watchexec loop running cargo run on the backend I’m working on, and a second one rebuilding the frontend on every save. That’s what the machine looks like while I’m working.
So a cold release build of the Formshive backend (660 dependencies) gets whatever’s left over. It takes sixteen minutes, and slows down everything else while it runs, including the watch loops I need responsive.
Meet crunch
crunch is a drop-in cargo replacement: it rsyncs your project to a remote machine, runs the cargo command there, and copies the artifacts back. It’s packaged in Guix:
$ guix shell crunch -- crunch --exclude "target,.git" build --release
The remote needs a Debian-ish box with Rust on it, and an SSH host entry called crunch in your ~/.ssh/config. Set ControlMaster auto and ControlPersist 5m on that entry; without connection pooling, every rsync pays for a fresh SSH handshake.
crunch keeps the project in ~/crunch-builds on the remote. Your local target/ is excluded from the transfer, so the remote target/ is the only build cache.
Build times
I tried three sizes of Hetzner’s CCX line, the dedicated-vCPU plans with physical cores instead of a share. All three ran AMD EPYC Milan in Falkenstein. Same project, same commit, cold each time; I wiped ~/crunch-builds and the cargo registry between runs.
| physical cores | RAM | cold build | warm rebuild | |
|---|---|---|---|---|
| ccx23 | 2 | 15 GB | 627s | 1s |
| ccx33 | 4 | 30 GB | 357s | 1s |
| ccx43 | 8 | 61 GB | 277s | 1s |
Going from two cores to four is 1.76x faster. Going from four to eight is only 1.29x; the critical path through the dependency graph and the serial link step don’t get faster with more cores. ccx43 costs twice as much as ccx33 and finishes 22% sooner, so ccx33 is my default.
The same build on my laptop took 964s, but that’s not a clean benchmark: a Guix source build was running at the same time. It’s the laptop as it is on a working evening, which is the number I care about, but it says nothing about the hardware.
Renting by the hour
Hetzner bills hourly, so I create a box when I need one and delete it when I’m done. The catch, straight from Hetzner’s docs: powering a server off does not stop billing. You pay until you delete it.
A small script, crunch-server, handles that:
$ crunch-server up # create, provision, wire up ssh
$ crunch-server status # what's running, and what it's cost so far
$ crunch-server down # delete it (this is the part that stops billing)
up pulls the API token from pass, uploads my SSH key, creates the server with a cloud-init that installs the toolchain, waits for cloud-init to finish (not just for sshd to answer), and writes a Host crunch block with the pooling settings into ~/.ssh/config. If a box is already up, it reuses it; each fresh up starts a new billed hour.
Two things I got wrong the first time:
- Host keys. Every new box gets a recycled IP, so the main
known_hoststhrows a host-key warning every time. The script writes to a separate~/.ssh/known_hosts.crunchand wipes it on teardown. - Native dependencies. This project pulls
pq-sysandopenssl-sys, solibpq-devandlibssl-devhave to be in the cloud-init, or the build fails twelve minutes in.
Round trip
- Bring-up: 61 seconds, from nothing to a box that answers
cargo --version - Teardown: 18 seconds
I expected three or four minutes and was ready to build a base snapshot. At a minute, it’s not worth it.
Hetzner rounds usage up to the hour, though. Three short-lived boxes in an afternoon cost three hours; keep one and reuse it.
Caveats
- One project, one run per configuration. This shows the trend, not precise figures.
- The warm rebuild is a no-op; nothing changed between runs. It confirms the remote cache survives and that syncing 5 MB of source costs nothing, but it’s not the edit-compile loop. That’s the number I still need.
- The first ccx43 attempt failed with
dedicated core limit exceeded. New projects have a cap on dedicated vCPUs; support raises it on request. - crunch’s author recommends fewer fast cores over many slow ones, and runs a Hetzner AX102 (Ryzen 9 7950X3D). Milan EPYCs at around 2 GHz are the “many slow cores” option, and the weak scaling above four cores matches that.
The AX102 is next. For now the build runs somewhere else, my watch loops stay responsive, and the whole experiment cost about a euro.