Ein Made Me Keep Going

EinVault's v1.0 login page
EinVault's v1.0 login page

The last time I wrote about this, there was a dog asleep on the bed who had no idea he was responsible for a GitHub repository. He's still asleep. The repository is now on version 1.0.1.

Back in March I posted EinVault to /r/selfhosted on New Project Friday and got absolute silence. Zero traction. I wrote a blog post about it anyway, shrugged, and figured that was the natural lifespan of My First Project: one person, one dog, one Docker container nobody else would ever run.

Twelve weeks and thirty-something releases later, that turned out to be wrong. So this is the long version of what happened between v0.1.1 and v1.0.1, who showed up, and what I actually built.

About that silence

The Reddit post got nothing. But "nothing" isn't the same as "nobody."

A handful of people found the repo on their own, spun it up, and started opening issues. Not many. A small group. But the right kind of small group, the kind that runs the thing on real hardware with a real pet and then tells you exactly what's annoying about it. Most of the roadmap below didn't come from me staring at a backlog. It came from those people.

I'll name them in a minute, because they earned it.

The list I wrote down

At the end of the first post I scribbled a "What's Next" list. Mostly to look like I had a plan. Here's that list, with the benefit of hindsight:

  • Email/ntfy notifications. Done in v0.13.0, alongside forgot-password.
  • S3-compatible photo storage so you're not stuffing images next to your container. Done in v0.10.0.
  • Immich integration for homelabbers who already sorted their photos. Also v0.10.0.
  • Attribution on entries. Done early, v0.2.3. Now every entry knows who logged it.
  • Localization. v0.2.0 laid the groundwork. Six languages now ship.
  • A demo site. Still the one item nagging at me. Asking people to spin up a container before they can see the UI is a real barrier, and I still haven't fixed it. Consider this a public IOU.

Crossing items off a list you wrote to sound organized is a deeply satisfying thing. Five out of six. I'll take it.

The people who showed up

If you only remember one part of this post, make it this one.

ragnarkarlsson is the reason a big chunk of EinVault exists. He asked for OIDC and then helped test it, which is how v0.5.0 shipped with single sign-on. He asked for a real multi-pet view, which became the Overview page in v0.6.0. He wanted reminders you could set by calendar and frequency instead of crude intervals, which turned into the whole recurrence engine in v0.7.0. He filed the overview UX paper-cuts that became v0.10.2. One person, an outsized dent.

albanobattistella opened the localization request and then just... sent the Italian translation. First community code contribution, v0.2.2. Nothing makes a side project feel real like a stranger handing you a language you don't speak.

SonGokussj4 pointed out that the journal was borderline unreadable in dark mode. They were right. v0.4.0 fixed it.

YellowJacket51 wanted to attach files, which grew into Documents with Paperless-ngx import in v0.14.0.

None of these were in my head when I posted to Reddit. The crickets were real. The people who got past the crickets were also real, and they were better at finding the gaps than I was.

The integration months

April and May were the integration grind, the part where the app stopped being "my thing for Ein" and started being software other people's setups had to slot into.

Storage went external in v0.10.0 with S3 and Immich, so your photos can live wherever you already keep photos. The journal learned to take video in v0.11.0, and then v0.12.0 added optional server-side transcoding so those videos actually play in every browser instead of just the one you uploaded from. Notifications landed in v0.13.0 over email and ntfy. Documents and Paperless came in v0.14.0.

Reminders quietly grew up the whole time. They started as a checkbox. By the end they had an undo window you could configure, real calendar-anchored recurrence, the option to log a health event when you complete one, and the good sense to surface a vet follow-up as its own reminder. A few of those migrations were one-way, which is the polite way of saying I made you back up your database first.

Making it not break

There's a release, v0.15.0, that on paper looks boring and is secretly my favorite.

That's the one where EinVault grew a test suite. Vitest for units, Playwright for end-to-end, a CI pipeline that runs all of it on every change. Exhaustive feature coverage, the kind where I can redesign half the app and trust the robots to tell me what I broke. The same release added full-text search with filters (@companion, #type, date ranges) and a subscribable ICS calendar feed so your pet's vet appointments show up next to your dentist ones.

Boring. Load-bearing. The reason the next part was even possible.

1.0: the glow-up

I'm a backend developer. I have said this before and the CSS has not gotten more forgiving since.

For 1.0 I went after the thing I'd been avoiding: the way it looked. Nearly every screen got reworked into a single design language. The member, admin, and caretaker views finally agree with each other. Dark mode behaves. Empty states tell you what to do instead of staring back blankly. Every page type has its own color now, so reminders, activity, and health read at a glance.

EinVault also got a mascot. Ein, the corgi, greets you on a fresh dashboard, waves from the login screen, and turns up looking appropriately confused when you hit a 404. Yes, he's named after the data dog from Cowboy Bebop. Yes, the seed users in the test database are Spike, Jet, Faye, and the rest. I contain no surprises.

And 1.0 shipped two-factor auth, the optional kind you turn on in Settings, with backup codes and the ability to require it across your whole household if you're the admin.

I leaned on Claude for the Tailwind and the front-end push, same as I admitted last time. The component structure and the logic are mine. The part where I stopped fighting flexbox at midnight was not done alone, and I'm not going to pretend otherwise.

1.0.1: the part where I couldn't leave it alone

1.0 went out and I immediately started noticing things.

The desktop sidebar stuttered when you scrolled to the bottom of a long page. Completing a reminder from its detail modal opened the little undo toast but never started the countdown, so it just sat there doing nothing. The weight trend card left a big empty hole next to a busy reminders list. "Next Vet" was showing whatever reminder happened to be next instead of an actual vet visit.

So 1.0.1 fixed all of that, moved the weight sparkline up into the dashboard hero, gave reminder and health rows their full width on mobile, tied the accent colors to what they describe everywhere those tags show up, and turned the mobile overview header into a real companion picker. Small stuff. The stuff you only see once everything else is done.

What I actually learned

My First Project posted to absolute silence and then spent three months becoming something a handful of people quietly depend on. That's not a viral success story. It's better than that. It's the version where the work was the point and the users were real.

The best side projects still start with a real, slightly embarrassing problem. Mine was leaving notes for a dog sitter. The thing I didn't know in March is that the second-best part comes later, when someone you've never met runs your dumb little app, finds the rough edge you missed, and helps you sand it down.

Ein's health log is still in there. I still use it everyday to track our life together. None of that changed. There's just a lot more around it now, and most of it has someone else's fingerprints on it.

EinVault is on GitHub. Pull it down, spin it up, tell me what's broken. Apparently some of you do.

Update (6/22/26): A demo version of EinVault is up to explore now! Check it out: https://demo.einvault.app/