← Blog

A resident portal for a homeowners’ association — in plain PHP and SQLite

DEENESRUUK

Not every project needs a JavaScript framework and a managed database. This one — a private web cabinet for the members of a Kyiv apartment owners’ association (ОСББ) — is deliberately boring underneath: PHP 8.3 talking to a SQLite file, served by nginx + PHP-FPM. No framework, no build step, no MySQL. It runs at osbb-wh.pp.ua.

The interesting constraint wasn’t the stack — it was the users. Residents are not developers; many are not young. Everything had to be obvious, fast on a cheap phone, and in their language.

What it does

  • Closed access — residents sign in against their apartment, with email activation for new accounts.
  • Announcements, documents and meeting minutes — the board posts, residents read, in Ukrainian and Russian.
  • A car registry with OCR. Residents add their cars with a photo, and Tesseract reads the licence plate from the image (with ImageMagick pre-processing) so the number is captured without manual typing.
  • Tickets to the board — a simple support thread between a resident and the management.
  • Polls with a strict one apartment, one vote rule.
  • Internal mail between residents (with soft-delete).
  • Live widgets — Kyiv weather and air quality, fetched server-side and cached so the client never calls a third party directly.
  • Admin tools — resident CRUD, role management, and content moderation.

Watching the building, safely

One of the more interesting pieces is the camera feed. Residents can watch the building’s entrance and yard cameras from inside their account — but the cameras themselves are never exposed to the internet.

The cameras speak RTSP, which browsers can’t play, and live on the private network. So a MediaMTX media server sits in front of them: it ingests the RTSP feeds and republishes them as browser-friendly, low-latency streams (HLS / WebRTC). The portal embeds those behind the resident login — the browser only ever talks to my server, never to a camera.

That one indirection buys a lot. The cameras keep their factory firmware off the public internet (where it would be found and brute-forced within hours), the heavy lifting of transcoding/repackaging is MediaMTX’s job rather than PHP’s, access is tied to the same account system as everything else, and there’s one place to revoke it. A similar server-side-proxy-plus-cache pattern is reused for the live Kyiv weather and air-quality widgets, so the client never calls a third party directly either.

Why no framework

For an app this size, a framework would have added more concepts than it removed. Plain PHP + PDO over SQLite means: one file is the whole database, backups are a copy, deployment is git pull, and there’s no dependency tree to keep patched. The UI leans on Bootstrap with a clean, government-portal-style look (in the spirit of Ukraine’s “Diia”), so it feels familiar and trustworthy to people who use state e-services.

The lesson

The hardest parts of a “simple” internal tool are never the framework choices — they’re the human ones: bilingual content, accessible forms, the OCR step that removes a chore, and access rules that a non-technical board can actually administer. Small, sturdy, and legible beats clever here.

Transparency: most posts here are drafted with AI assistance, and the non-English versions are machine-translated by a local LLM and then reviewed. Spotted an error? Please let me know.