Home Cloud Operating System
Status: Early alpha. Core infrastructure and web UI working.
Self-hosting's hard part isn't installation. Every platform has solved that. The hard part is when you add a second service and realize the first one doesn't know it exists.
To connect two services manually, you typically:
On every other platform, you are the integration layer — the person who knows which credentials go where and how the pieces fit together. You don't configure services. You configure the relationships between services. That knowledge lives in your head, not in the platform.
Bloud flips this. Apps declare what they provide and what they consume. The system holds the integration knowledge and wires everything automatically - on install, on restart, forever.
graph LR
DB[(Database)]
IP[Identity Provider]
S1[Service]
S2[Service]
RP[Reverse Proxy]
DB -->|credentials| S1
DB -->|credentials| S2
DB -->|credentials| IP
S1 -->|SSO client| IP
S2 -->|SSO client| IP
S1 <-->|API key| S2
S1 -->|route| RP
S2 -->|route| RP
IP -->|auth middleware| RP
Every edge in this graph is a manual step on every other platform. Bloud generates all of them and regenerates them correctly every time a service starts.
Bloud runs as a bootable ISO. Flash it to a USB drive or deploy it to a VM.
For development, you'll need a NixOS machine (see Local Development below).
git clone https://codeberg.org/d-buckner/bloud.git
cd bloud
npm run setup # Install deps, build CLI
./bloud setup # Check prerequisites, apply NixOS config
./bloud start # Start dev environment
Access the web UI at http://bloud.local (port 80, through Traefik).
| Category | Apps |
|---|---|
| Infrastructure | PostgreSQL, Redis, Traefik, Authentik |
| Media | Jellyfin |
| Productivity | Miniflux (RSS) |
| Network | AdGuard Home |
| Utility | qBittorrent |
Every app declares its integrations in metadata.yaml:
# example metadata.yaml
integrations:
database:
required: true
compatible:
- app: postgres
default: true
sso:
required: false
compatible:
- app: authentik
When you install an app, Bloud builds a graph of everything it needs. Dependencies with only one compatible option are auto-selected. If postgres isn't installed yet, it goes in first. If it's already there, the new app just gets wired to it.
The graph also determines execution order — infrastructure first, then dependents:
Level 0: postgres, redis ← No dependencies
Level 1: authentik ← Needs postgres + redis
Level 2: jellyfin, miniflux ← Need postgres and authentik
Each app has a module.nix defining how to run the container:
mkBloudApp {
name = "miniflux";
image = "miniflux/miniflux:latest";
port = 8085;
database = "miniflux"; # Auto-creates postgres DB and user
environment = cfg: {
BASE_URL = "${cfg.externalHost}/embed/miniflux";
};
}
The mkBloudApp helper handles systemd services, podman networking, volumes, and dependency wiring. When you enable an app, NixOS creates everything atomically.
SSO, routing, and credentials are wired automatically by the platform — apps don't configure those themselves. What configurators handle is app-specific setup that can't be expressed in Nix: creating directories, writing config files, setting app-specific defaults.
// PreStart: runs before the container starts
func (c *Configurator) PreStart(ctx context.Context, state *AppState) error {
// Ensure directories exist
if err := os.MkdirAll(filepath.Join(state.DataPath, "config"), 0755); err != nil {
return err
}
// Write app config with required settings for Bloud's embedding
ini, _ := configurator.LoadINI(configPath)
ini.EnsureKeys("Preferences", map[string]string{
"WebUI\\HostHeaderValidation": "false",
"WebUI\\CSRFProtection": "false",
"Downloads\\SavePath": "/downloads",
})
return ini.Save(configPath)
}
// PostStart: runs after the container is healthy
// Used for apps that require API calls to configure (e.g. Authentik)
func (c *Configurator) PostStart(ctx context.Context, state *AppState) error {
return nil // most apps are fully configured by PreStart
}
Configurators always write the desired state — running them again produces the same result.
When a new integration becomes available, existing apps that can use it get automatically reconfigured:
Service A installed (no SSO)
Identity provider installed
│
▼
Graph: "Who declared an SSO integration?"
→ Service A
│
▼
Service A marked for reconfiguration
Service A restarted in dependency order
PreStart + PostStart wire the new integration
No user action needed. Apps gain integrations as you install their dependencies.
Each Bloud host runs exactly one instance of each infrastructure service. All apps share them:
Apps get SSO automatically. Three strategies depending on the app:
| Strategy | How It Works | Example Apps |
|---|---|---|
| Native OIDC | App handles OAuth2 itself; Bloud provides credentials via env vars | Miniflux |
| Forward Auth | Traefik checks auth with Authentik before the request reaches the app | AdGuard Home, qBittorrent |
| LDAP | Authentik LDAP for apps that don't speak OAuth2 | Jellyfin |
All three are configured automatically at install time.
bloud/
├── apps/ # App definitions
│ ├── miniflux/
│ │ ├── metadata.yaml # Integrations, SSO, port, routing
│ │ ├── module.nix # Container definition
│ │ └── configurator.go # PreStart/PostStart hooks
│ ├── postgres/
│ ├── authentik/
│ └── ...
│
├── services/host-agent/ # Go backend
│ ├── cmd/host-agent/
│ ├── internal/
│ │ ├── orchestrator/ # Install/uninstall coordination
│ │ ├── catalog/ # App graph and dependency resolution
│ │ ├── nixgen/ # Generates apps.nix, Traefik config, blueprints
│ │ ├── store/ # Database layer
│ │ └── api/ # HTTP API
│ ├── pkg/configurator/ # Configurator interface
│ └── web/ # Svelte frontend
│
├── nixos/
│ ├── bloud.nix # Main NixOS module
│ ├── lib/
│ │ ├── bloud-app.nix # mkBloudApp helper
│ │ └── podman-service.nix # Systemd service generator
│ └── generated/
│ └── apps.nix # Generated by host-agent on install
│
└── cli/ # ./bloud CLI (Go)
Requires a NixOS machine (physical or VM with the dev-server config applied).
npm run setup # Install deps + build ./bloud CLI
./bloud setup # Check prerequisites, apply NixOS config
./bloud start # Start dev environment
Native NixOS mode (development on a NixOS machine):
./bloud start # Start dev environment
./bloud stop # Stop dev services
./bloud status # Show status
./bloud logs # Show logs
./bloud attach # Attach to tmux session (Ctrl-B D to detach)
./bloud shell [cmd] # Run a command or open a shell
./bloud rebuild # Apply NixOS config changes
Proxmox mode (ISO integration testing, set BLOUD_PVE_HOST):
./bloud start [iso] # Deploy ISO → create VM → boot → check
./bloud start --skip-deploy # Reuse existing VM, re-run checks
./bloud stop # Stop VM
./bloud destroy # Destroy VM
./bloud shell [cmd] # SSH into VM
./bloud checks # Run health checks
./bloud install <app> # Install app via API
./bloud rebuild # Apply changes to the running dev machine
# Check host-agent logs
journalctl --user -u bloud-host-agent -f
# Check app container logs (includes PreStart/PostStart output)
journalctl --user -u podman-miniflux -f
# Restart a service to re-run configurators
systemctl --user restart podman-miniflux
# Check install plan (shows resolved dependency graph)
curl http://localhost/api/apps/miniflux/plan-install
apps/myapp/metadata.yaml — integrations, SSO strategy, portapps/myapp/module.nix — container definition using mkBloudAppapps/myapp/configurator.go — PreStart, HealthCheck, PostStartservices/host-agent/internal/appconfig/register.goSee docs/contributing-apps.md for details.
Contributions welcome.
Open an issue with a clear description and steps to reproduce for bugs.
AGPL v3 — See LICENSE for details. If you modify Bloud and offer it as a service, you must share your changes.
Built with NixOS, Podman, Systemd, Go, and Svelte.