head2head · jimmyhoughjr.net
The same ~small auth-and-storage service, implemented twice and swapped live behind the same Dokku app on an 8-core ARM64 pi (16 GB). Identical API, session format, and storage files — the frontend never noticed. Measured July 7, 2026.
| Metric | Node 22 | Swift / HB2 | Edge |
|---|---|---|---|
| Process RSS after boot | 54 MB | 24.7 MB | Swift |
| /health latency, LAN avg (n=20) | 10.5 ms | 11.6 ms | tie |
| /health latency, LAN max (n=20) | 20.3 ms | 14.4 ms | Swift |
| Deploy, wall clock | ~15 s | 299 s (450 s cold) | Node |
| Attempts to first good deploy | 1 | 3 | Node |
| App payload in image | 18 KB source | 112 MB static binary | Node |
| Runtime base image | node:22-alpine | ubuntu:noble | — |
| Lines of code (incl. shared HTML) | 367 | 489 | Node |
| Dependencies | 0 | 2 (Hummingbird, swift-crypto) | Node |
| Mac dev-loop rebuild | none needed | 3–6 s incremental | Node |
Both implementations expose the same routes, mint the same HMAC-SHA256 session cookies, and write the same one-JSON-file-per-user storage layout. That was proven, not assumed: a session token minted by the Node code validated against the live Swift server, and an authed write/read round-trip ran through Cloudflare against production. The watts app needed zero changes; Dokku's config, domain, and storage mount carried over untouched.
Node deployed right on the first push. Swift took three: the code compiled cleanly on the Mac's Swift 6.3 toolchain, then failed twice on the pi's Swift 6.1 Linux image — once because Linux swift-crypto's SymmetricKey isn't Sendable under strict concurrency, once because Linux Foundation lacks URLResponse(). Each round-trip to find that out cost ~8 minutes of pi build time. That's the real Swift tax on this setup: not runtime performance, but the length and platform-fidelity of the feedback loop when Dokku builds on the device itself.
Two mitigations worth noting: failed builds never touched the running service (Dokku keeps the old container), and the Dockerfile could cache dependency compilation in its own layer to cut code-change rebuilds further.
For a service this small, both are overkill and both are fine. Swift genuinely halves the memory footprint and trims tail latency — on a 16 GB pi hosting a growing pile of containers, that's the number that compounds. Node wins everything about the iteration loop: 20× faster deploys, first-try correctness, no cross-platform surprises, a fifth the image payload.
The Swift version is what's running now. It earned the slot on memory, and reverting is one git push from the Node repo if the build friction ever outweighs it.
Checking sign-in…
Methodology: latency is 20 sequential LAN curls per implementation via the pi's nginx (Cloudflare excluded); RSS from ps inside each container after boot; deploy times are wall-clock git push to "application deployed." Single-run wall clocks — directional, not benchmarks. Built by Jimmy Hough Jr & Claude · Donations appreciated · Powered by Roost.