Skip to content

Repository files navigation

bloud

License: AGPL v3 Status: Alpha

An open-source home server inspired by kubernetes. Self-hosting is kind of unreasonably hard. Not the installing part. The part after.

In bloud, you install an app and the reverse proxy, unified login, and all inter-app integrations happen for you.

try it

Debian 13, x86_64.

The script is short, and you should read it before piping it to a shell: install.sh. To do it by hand instead, grab the .deb from the releases page and run sudo apt install ./bloud_*.deb.

curl -fsSL https://raw.githubusercontent.com/d-buckner/bloud/main/install.sh | sudo sh

Open the dashboard at http://localhost:8080. Set your host under Settings, then Hosts. Install Jellyfin.

Please don't expose bloud to the public internet yet. It's alpha, it serves plain HTTP, and there is no mechanism yet for getting security updates to apps or to Bloud itself. Keep it on your LAN for now.

why an engine

Getting a container running isn't the hard part, podman run will do that. The hard part is everything the container needs from the rest of the system: a route in Traefik, an OIDC client or an LDAP binding in Authentik, a database with a password nobody has to copy by hand, and all of it still correct a year later.

Bloud handles that with a reconciliation loop, the same shape as a Kubernetes controller. Each app declares what it provides and what it consumes, the engine resolves those declarations into a graph, works out the wiring, and then keeps checking its work.

Two rules hold that together:

  • Single writer. Only the orchestrator writes lifecycle state or performs side effects. HTTP handlers submit intents and never mutate anything themselves.
  • Idempotent configurators. PreStart and PostStart run on every cycle, not just on install. PreStart brings the config on disk in line with what the app should have, and does nothing when it already matches.

A template can write one config file once. Nothing but a loop keeps every config file in someone's homelab correct through upgrades, crashes, and reboots.

the full graph

Every app, the containers each one declares, and the edges that connect them.

CI draws this rather than anyone updating it by hand. A headless browser renders it with the same components that draw the developer graph inside a running Bloud, fed the catalog snapshot that ./bloud depgraph --json builds from every app's metadata.yaml. The catalog is the only input, so the picture can be drawn on a machine with nothing installed.

The Bloud catalog: every app, the containers it declares, and the integrations between them

The text form of the same graph is in docs/architecture/dependency-graph.md, and that is what --write refreshes and --check gates. Add an app and both of them regenerate; a merge that touches neither the catalog nor the renderer leaves both alone.

npm run graph:image   # rebuild the picture locally

catalog

The catalog is small on purpose. A half-supported app is worse than no app at all, because it looks like an answer right up until the first time you depend on it. Every entry here carries the same contract and we verify each one of them: install, shared login, persistence, reboot, removal.

./bloud catalogdoc --write generates the list below from every app's metadata.yaml, the same file the graph is drawn from, so a new app shows up here whether or not anyone remembers to mention it.

  • AFFiNE: AI-native knowledge base that unifies docs, databases, and whiteboards
  • Calino: Browser calendar for the CalDAV calendars Bloud already serves
  • Hermes: Self-improving AI agent with persistent memory, scheduled automations, and a web dashboard
  • Home Assistant: Open-source home automation platform
  • Immich: Self-hosted photo and video management
  • Jellyfin: Free software media system for streaming movies, TV, and music
  • Navidrome: Modern music server and streamer compatible with Subsonic/Airsonic clients
  • Paperless-ngx: Document management system that turns scans and PDFs into a searchable archive
  • Prowlarr: Indexer manager that syncs indexers to Sonarr, Radarr, and other PVRs
  • qBittorrent: BitTorrent client with a web interface
  • Radarr: Movie collection manager for Usenet and BitTorrent users
  • Radicale: CalDAV and CardDAV server for calendars, contacts, and to-do lists
  • Seerr: Request and discovery manager for your media server
  • Sonarr: PVR for TV series that monitors, grabs, and organises episodes
  • Vaultwarden: Lightweight, Bitwarden-compatible password manager

Plus the system apps: Authentik (security) and Traefik (network).

The media stack is where this shows up most. Sonarr, Radarr, Prowlarr, qBittorrent, and Seerr are five separate projects that only become a pipeline once they're wired to each other, and that wiring is the tedious part by hand: add qBittorrent as a download client in each PVR with its own category and folder, get the indexers from Prowlarr into both, then connect Seerr to Jellyfin and to the PVRs so a request actually lands somewhere. Bloud does all of it from the declarations, so five apps is five clicks rather than an afternoon of copy-paste.

We ship no media, no indexers, and no trackers, and Bloud has no view on what you point it at. It's built for things you have the right to use.

one login

Strategy Apps
LDAP Jellyfin, Radicale, Seerr
Forward auth Calino, Navidrome, Prowlarr, qBittorrent, Radarr, Sonarr
Native OIDC AFFiNE, Hermes, Home Assistant, Immich, Paperless-ngx, Vaultwarden

Clients that speak a native protocol, a Subsonic player or a TV app talking to Jellyfin, keep the login path their protocol defines. Bloud fronts the web UIs and stays out of the way of those.

what is not done yet

Alpha in specific ways, so you know which gaps you're signing up for.

  • No TLS. Plain HTTP only, and this is the biggest gap. Let's Encrypt on Traefik, or Tailscale Serve, is the planned follow-up. Fine on your LAN if you accept it; not acceptable off-LAN.
  • Sharing in progress. Core sharing works. Tailnet outpost auth is still in development.
  • No bloud init. First-run host config happens in the dashboard.
  • Debian 13 only. A support contract has to be true somewhere before it spreads.
  • The loop is not yet hardened against every failure mode. The auth bypass is remotely forgeable and is the current shipping blocker. Full ledger: docs/operations/tech-debt.md.

developing it

npm run setup            # pick backend, check prereqs, build ./bloud
./bloud dev              # build + deploy + run (Ctrl-C to stop)
./bloud install jellyfin # through the real API
./bloud validate --tier fast

Backends: Lima on macOS (automatic), QEMU on Linux (default), native on Linux CI (BLOUD_BACKEND=native). On the native backend ./bloud dev hot-reloads: save a Go file and the host-agent rebuilds and restarts in about 3s with the app containers left running, and the dashboard hot-reloads through vite. Use --no-watch for the one-shot build-deploy-run. Add --reset to wipe the runtime first (the same wipe as ./bloud reset -y, no prompt) and come up from empty data. Apps land at http://<app>.localhost:8080.

The integration tier runs the real graph path rather than a shortcut: host-agent deployed as a systemd user service, Jellyfin installed through POST /api/apps/jellyfin/install, the orchestrator converged, behavioral tests run inside the VM. Tests assert what the app's own API reports, not what our config values happen to be.

./bloud validate --tier integration   # real install/reconcile flow
./bloud e2e lifecycle                 # install -> restart -> uninstall -> cleanup

ai disclosure

While the high level technical design and architecture are done by me personally, much of the low level implementation is done by LLM. For me, this is done with local models hosted on my own hardware (qwen3.8-flash-next at the time of writing). If this does not align with the values you want your software to have, I understand and this project may not be for you.

further reading

license

AGPL v3. See LICENSE.