home menu

Single-origin deployment with bhvr

Fried my Pi using a 10V solar panel without a buck converter.

On this page

I fried a Raspberry Pi Zero 2 W. A 10-volt solar panel running straight into the Pi's 5 V USB was not the move.

While I waited on a new board and a buck converter, I rebuilt the site from scratch.

Old stack

Node.js (ARMv6 build)  
PNPM  
Hono + hono/jsx  
Cloudflare Tunnel

Everything lived in one server file using hono/jsx. HTML, styles, scripts, and API data all came out of the same Hono route handlers:

app.get("/", (c) => c.html(`<h1>Air Quality</h1>`));

It worked, but the file was cramped: no hot reload without a full restart, no separation between UI and API, boilerplate for the smallest content tweak.

Markdown rendered server-side. The live-data API (air quality, device location) ran on the same routes serving HTML. Everything came out of port 3000 behind a Cloudflare Tunnel. When the Pi slept, the site slept. That was the point.

Rebuild

I dropped Node and PNPM. Bun became the runtime and package manager. bhvr gave me a monorepo layout for Bun + Hono + React + Vite. The single-origin tunnel stayed: one server, one port, one pipe.

New stack

bhvr (Bun + Hono + Vite + React)  
Cloudflare Tunnel

Three workspaces:

client/   → React + Vite frontend  
server/   → Hono API + static server  
shared/   → TypeScript types used by both

The React app builds separately, and its static files drop into server/dist/client. One Bun process on port 3000 serves both the frontend and the API.

Rebuilding locally

git clone https://github.com/iammatthias/feral-pure-internet.git
cd feral-pure-internet
bun install

bun run build && bun run build:server
mkdir -p server/dist/client
cp -r client/dist/* server/dist/client/

bun run server/dist/server/src/index.js

A static React site and Hono API are now live at localhost:3000 and localhost:3000/api/hello.

Pi deployment

This time, a proper 5 V buck converter stepped the solar panel's 10 V output down. No smoke.

curl -fsSL https://bun.sh/install | bash
echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Clone to /opt/feral/app, run bun install, and build as above. Wrap it in build.sh for automation.

Systemd

# /etc/systemd/system/feral.service
[Unit]
Description=Feral, single-origin Bun server
After=network-online.target

[Service]
User=feral
WorkingDirectory=/opt/feral/app
Environment=WAQI_TOKEN=•••
ExecStart=/home/feral/.bun/bin/bun run server/dist/server/src/index.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now feral

This replaces PM2. It restarts on crash or reboot without "save" or "startup" steps.

Tunnel on a single port

# /etc/cloudflared/config.yml
tunnel: <UUID>
credentials-file: /etc/cloudflared/<UUID>.json

ingress:
  - hostname: feral.pure---internet.com
    service: http://localhost:3000
  - service: http_status:404
cloudflared tunnel create feral
cloudflared tunnel route dns feral feral.pure---internet.com
sudo systemctl enable --now cloudflared

UI and API both live at https://feral.pure---internet.com. No ports, no CORS, no dev/prod shims.

Update flow

cd /opt/feral/app
git pull --ff-only
bun run build && bun run build:server
mkdir -p server/dist/client
cp -r client/dist/* server/dist/client/
sudo systemctl restart feral

A post-merge hook or cron job will automate it. Takes under 5 seconds.

Before and after

Old New
Runtime Node v20 (ARMv6 build) Bun
Package mgr PNPM None (Bun native)
Front-end hono/jsx React + Vite (via bhvr)
Dev build Rollup + tsc Bun + Vite
Deployment PM2 systemd
Architecture Single file, no separation client/server/shared
Serving style hono SSR + inline JSX Static bundle + JSON API
Origin setup Single-origin Single-origin

The app is tiny, ephemeral, and still solar-powered. Rebuilding with bhvr added real structure: client and server actually separated, deploys and updates much simpler, lighter runtime (just Bun and a couple of systemd units), and no more crashing when I tweak a <div>.

Single-origin deployments suit bhvr well, especially when the whole stack fits in a few megabytes on a Pi Zero 2 W.

collection
posts
rkey
1747182886660-single-origin-deployment-with-bhvr
record cid
bafkreibf6c3xwvhiszc2y6hwzxaxq7qizamhgw4n22jhzlfnz4qe2dvp3i
record
https://content.farfield.systems/api/entries/1747182886660-single-origin-deployment-with-bhvr
created
updated