Context
Stella is a self-hosted web app that my partner and I use at home every day. I started it in February 2026 and it grew into one place for running the household: budgeting on a monthly pay cycle, groceries and pantry stock, recipes, voice notes, trip planning, comparing rental listings for a move, and live readings from a sensor in our flat. It runs on a server at home and installs on our phones as a PWA. An earlier budgeting prototype of mine, MintCondition, was folded into it.
My role
I'm the sole developer, and one of the two people who use it. I designed, built and run all of it: the Next.js app, the PocketBase schema and access rules, the Docker image and the Coolify deploy on the home server, the scheduled jobs, and the prompts behind every AI feature. I use AI coding agents for a lot of the implementation, and I plan the work, review every change and decide what merges. Merging to main deploys it.
Problem
Keeping a household running meant a lot of small tracking that nobody enjoys. Food expired at the back of the cupboard, staples ran out before anyone noticed, and a budget is only as good as the transactions someone bothers to type in. I wanted one app where capturing data takes as little typing as possible, where it warns us before something runs out or goes off, and where the data stays on hardware we own.
Constraints
- One developer. I build it in my own time, so anything that needs constant attention doesn't survive.
- Hardware at home. Everything runs on one home server next to other services, so disk and memory matter, and a sensor posting once a minute adds up quickly.
- Private data. It holds our finances and other personal records. I wanted them on a machine I control, not in a third-party cloud.
- Low running cost. Gemini is the only paid service. The app tracks what it spends and rate-limits AI calls per user.
- Secure anyway. Being on the home network is no excuse for weak security, so it is built as if it were public.
Approach and architecture
Stella is a Next.js 16 app built as a Docker image. Coolify builds and deploys it on the home server when I merge to main. PocketBase holds the data, the logins and uploaded files. The browser never talks to PocketBase: every read and write goes through a server route that checks the session and validates the input with zod, and the session lives in a cookie page scripts can't read. PocketBase's access rules are a second line of defence, kept in versioned migrations.
Scheduled work
A cron job on the server calls the app's cron routes with a shared secret. Each morning one of them checks the pantry for items close to their expiry date and sends a push notification. The pantry also flags items that are running low.
Sensor data
A sensor in the flat posts temperature and humidity once a minute: 1,440 readings a day. Day and week charts read hourly averages instead: 24 a day, and a rolling week of 168. Every night a PocketBase hook archives the previous day as one summary row and deletes raw readings older than 14 days.
AI features
Gemini reads scanned receipts into the pantry, generates recipes from what we have, plans trips and summarises the weather from Open-Meteo. Voice notes are transcribed by a Whisper server at home, and Gemini turns the transcript into structured notes. Serwist makes the app installable, with an offline page and no caching of API responses.
Key decisions and trade-offs
Run the database at home
- Chose
- Self-hosted PocketBase in Docker on the home server
- Over
- A hosted database and auth service
- Cost
- I own the backups, upgrades and uptime
PocketBase gives me a database, logins, file storage and a place to run scheduled hooks in one small service that sits next to the app. There's no monthly bill and our data never leaves the house. The price is that I'm the operations team.
Keep raw sensor readings short-lived
- Chose
- Hourly averages for the charts, a nightly daily summary, raw readings deleted after 14 days
- Over
- Keeping every raw reading and aggregating when a chart loads
- Cost
- Minute-by-minute detail older than two weeks is gone
One reading a minute is fine for a live view but wasteful for a weekly chart, and on a home server the table would grow for ever. Nobody looks at minute-level humidity from last month, so the long view only needs daily numbers.
Make each AI feature configurable at runtime
- Chose
- A settings page that stores the Gemini model and prompt for each AI task in the database
- Over
- Model names and prompts hard-coded in the source
- Cost
- A settings collection, a cache and a fallback path that all need testing
Models and prices change often, and tuning a prompt shouldn't need a deploy. Each task falls back to the built-in model and prompt if its settings are missing, and a failed call retries once on a fallback model. It also lets me put a cheaper model on simple jobs.
Outcome
Stella has been in daily use since early 2026. From July to September 2026 it raised 15 pantry warnings, 12 for low stock and 3 for expiry, counted in the database. The sensor runs around the clock, and 1,440 readings a day are kept as 24 hourly averages, so a week's chart reads 168 rows.
It has saved us money, mostly through less wasted food and seeing overspending earlier in the month. Recent work made the budgeting more rigorous, and gave us a more objective way to compare apartments while we plan a move, with listings on a map scored against weights we agreed on.
Reflection
The one call I'd reverse: for the first seven months the browser talked to PocketBase directly, and most collections had open access rules, because the app was only on our home network. An audit I ran in September moved every read and write behind server routes, locked the rules down in a migration and added tests and CI. The same audit found that the nightly sensor job had been failing without anyone noticing. I would put the server boundary and CI in from the first commit, because retrofitting them touched almost every route.
Being on the home network is not a security model.
Next is a voice agent, so we can ask Stella questions and log things by speaking instead of typing.
