Eli Bakal

e-pi

Six ingestors on a Raspberry Pi turn the yard's birdsong, the meter readings, and the garden into one private dashboard I check every morning.

Solo builder · May-July 2026 · In progress

Raspberry Pi 5 · Docker Compose · TimescaleDB · Next.js · TypeScript · Python · Tailscale · MCP Python SDK

My house was already radiating data all day, power draw, water use, weather, birdsong, soil moisture, so I built a Raspberry Pi to catch it, and now it tells me which bird just landed in the yard and whether the solar panels are paying for themselves.

Context

I wanted to learn two things at once: how to run an always-on box for myself, and what my own house's data actually looks like once someone bothers to collect it. A Mac Mini was the obvious pick for a home server that year, and mine was sold out everywhere I checked, so I went smaller instead, a Raspberry Pi, cheap enough to experiment on and small enough to leave running in a closet. Self-hosting was the itch. The house supplied the other half of the idea: it was already throwing all of that off all day, unread, so the project became catching what the house gives off and putting it somewhere I could actually see it. I designed, built, and operate all of it alone: 410 commits over the build (git compresses the real May-to-July 2026 timeline), 26 design specs, 12 SQL migrations, and about 17,000 lines of Python, TypeScript, and SQL.

Problem

The naive version of this is easy: point a Pi at some sensors and graph it. The hard part is making it trustworthy without making it boring. A bird detection model calls out a species from audio constantly, and not every call is right, so I needed the live feed to stay exciting while the historical record stayed trustworthy, two different bars for the same stream. And the one rule none of it could bend: nothing on this Pi touches the public internet. The live feed shows when I'm home and what's growing in my garden beds; the only acceptable way to see it from my phone is over Tailscale, the private mesh network connecting the Pi, my Mac, and my phone. That rule is simple until the platform has to survive a reboot, grow a database for years on an SD card, and answer questions from an LLM, all without ever opening a port.

Decisions

A couple of decisions cut across every stream. The dashboard binds to the Pi's Tailscale IP instead of 0.0.0.0 to keep it off the public internet, and that same rule is what took it down for four days before I traced it. 1Decision 01The dashboard binds to the Pi's Tailscale IP instead of 0.0.0.0 to keep port 3000 off the public internet, but after a reboot Docker restored the container before Tailscale had assigned that IP, so the bind failed and the dashboard died and stayed dead. Constraint: Rebinding to 0.0.0.0 and firewalling it back would invert the safe-by-default posture the whole project depends on. Chose: I wrote a small systemd unit that waits for the Tailscale IP to actually appear on the interface before starting only the dashboard container, so the database and ingestors still boot immediately. Traded: It's one more moving part outside Docker Compose, and a stalled Tailscale connection now shows up as a failed systemd unit instead of a silently dead container. Once the dashboard existed, I wanted to ask it things it wasn't built to answer, like how closely HVAC energy use tracked outdoor temperature, without opening the database to the internet. 2Decision 02An LLM answering cross-domain questions over the Pi's data needs real SQL access, but the no-public-exposure rule rules out the hosted claude.ai connector path, which needs a public HTTPS endpoint. Chose: I built a small MCP server that exposes the database over Tailscale only, to Claude Desktop and Claude Code, backed by a dedicated read-only Postgres role with no insert, update, delete, or DDL grants. Traded: It only reaches Claude Desktop and Claude Code over the tailnet, not the claude.ai web app, which needs a public OAuth endpoint the project's own rule forbids.

The dashboard

Seven tabs, one per thing the house gives off, laid out the way the dashboard is. They arrived in a different order as I built, power and water first, then weather, then birds, then the garden, then system health last, once everything else needed something to vouch for it.

The e-pi summary tab: power and water cards on top, a birds panel and a weather, garden, and system row below
The morning view, power and water up top with today set against yesterday, birds, the garden, and system status below.

This is the screen I actually check. One card each for the streams that move fastest, and the whole point of the project compressed into a glance: if nothing here is surprising, the house is fine and I can put my phone down.

6
data streams: power, water, weather, birds, garden, Pi vitals
33M
energy rows a year from one circuit monitor alone
14
Docker containers on one Pi
410
commits over the build, May-July 2026

It's still a system I operate alone, no second admin, no backup for the Pi's SD card beyond the GitHub remote holding the source. The MCP server is the newest piece, and I haven't yet asked it a real cross-domain question I couldn't answer by eye, which is its own kind of honest result: the plumbing works, I just haven't needed it for anything I couldn't already see.

Gallery

Add-planting form with a 25-plant catalog search and an optional harvest-date override
Add-planting form with a 25-plant catalog search and an optional harvest-date override
Expanded carrot row showing hardiness zone match, sowing guidance, and a personal note
Expanded carrot row showing hardiness zone match, sowing guidance, and a personal note