Generator
Merges module runtime files, rejects invalid dependencies or placement, and renders the intermediate model, Quadlets, targets, and host operations.
manifest + modules → stack.ir.json → bundle/
Alpha · reproducible self-hosted infrastructure
Choose the services. Generate the system.
Put the services you want in one flat manifest and pin each module to an exact revision. Platform Zero merges their runtime contracts, checks the dependency and placement rules, and renders a Podman and systemd bundle you can inspect before it changes a host.
Open source · exact module pins · no shared container daemon · five rootless user domains · validate before activation · release rollback
Alpha software: the architecture works on the project’s test stack, but interfaces and module contracts are still changing. Expect hands-on setup, debugging, and upgrades.
01 / your inputs
02 / generated output
● deterministic output / host unchanged until activation / verification required
What Platform Zero actually does
It is a runtime generator, not a fixed bundle of apps.
The current alpha path reads one flat site manifest and a workspace of pinned module checkouts. Each selected module contributes a stack.runtime.yaml; the generator merges them into one intermediate model, applies the placement policy, and renders a self-contained Podman bundle.
Service configuration, container definitions, dependencies, and tests stay with the service module. Site-specific selection and values stay with the site. The generated bundle records what was selected, so a service can change without turning the host into another one-off installation.
Where each decision belongs
When something changes, there is a clear place to change it: generic assembly lives in the generator, service behavior lives in a module, and the choices for one deployment live in its site configuration.
Merges module runtime files, rejects invalid dependencies or placement, and renders the intermediate model, Quadlets, targets, and host operations.
manifest + modules → stack.ir.json → bundle/
Package one service or a related group: its runtime contract, configuration, container context, dependencies, and module-owned tests.
stack.runtime.yaml + config + tests
Selects modules, pins their Git revisions, supplies deployment-specific configuration, and identifies the target environment.
selection + pins + site values
List the modules you want in one flat manifest and check out the exact revisions beside the generator.
Merge the selected runtime contracts, apply Podman placement, and write a deterministic bundle through an atomic staging directory.
./generate.sh --site site/manifest.json --modules-dir ../modules --backend podman --output bundle
Check the bundle metadata, final environment files, Quadlets, and generated systemd units without changing the host.
./ops/install-podman-bundle.sh --bundle . --env-dir /path/to/env
Install a versioned release, switch the current links atomically, start every runtime domain, and restore the previous release if activation fails.
./ops/install-podman-bundle.sh --bundle . --env-dir /path/to/env --activate
Why Podman fits this model
The current alpha runs ordinary apps and container-creating tools in separate rootless operating-system accounts. Podman is a good match for that layout: containers do not require one central privileged daemon, while an API socket can be added inside only the user domain that needs it.
In a common Docker setup, access to the main socket can mean broad control over the daemon and what it can reach. If CI, notebooks, tests, and long-lived apps share that control plane, the potential blast radius grows.
In the generated layout, each socket controls its owning user’s rootless runtime. UNIX permissions, separate subordinate-ID ranges, storage, and user systemd managers are used to keep those control planes apart.
A notebook or CI job can create containers without receiving the application socket or rootful control plane. This limits exposure to that service account’s host permissions and rootless runtime.
Quadlet turns generated container definitions into systemd units. Services start, stop, log, fail, and restart through the host’s existing supervisor rather than depending on one always-on container daemon.
Tools such as JupyterHub and Forgejo Runner can use a Docker-compatible Podman API. Where needed, Platform Zero enables a separate, socket-activated API service for the owning user instead of sharing one socket across the stack.
The precise comparison: this is not a claim that Podman uniquely makes isolation possible. Docker can also be configured with rootless or multiple daemons. Podman’s daemonless architecture simply fits Platform Zero’s generated per-user model well. These boundaries reduce blast radius; they do not replace careful permissions, updates, or review of the remaining rootful services.
Security follows the deployment
Platform Zero does not put every container behind one privileged socket. Placement, identity, validation, secret delivery, and rollback are explicit parts of the generated bundle, so the important boundaries can be reviewed before activation.
Modules declare whether they need host access. Ordinary applications run in the generated rootless domain; rootful placement remains a visible exception for infrastructure that actually needs it.
rootful only when host access is required
JupyterHub, Forgejo Runner, managed test runners, and Workload Spawner each have a separate user, runtime directory, release root, and Podman socket.
webservices ≠ jupyterhub ≠ test-runners ≠ forgejo-runner ≠ workload-spawner
OIDC-capable services connect to Keycloak. Proxy-auth services accept identity headers only from configured proxy ranges, and enabled clients must have the credentials they require.
Keycloak clients + explicit trusted proxy CIDRs
Encrypted site inputs can remain bundled. The installer can render them before activation or accept a separate directory of final per-service environment files, then installs restricted copies into each owning domain.
encrypted inputs or separate env files → restricted runtime files
Without --activate, the installer checks the bundle manifest, environment files, Quadlets, and generated units, then exits before installation.
validate first → activate deliberately
Versioned releases switch through current links, and failed activation restores the previous release. Eligible registry-managed daemons update with Podman rollback and freeze further updates after a failed run.
versioned activation + guarded auto-update
The result is inspectable: stack.ir.json shows placement and dependencies; the generated directories show exactly which Quadlets, users, sockets, and releases will be installed.
Capabilities you can compose
Start with the capabilities people need: shared files, chat, monitoring, notebooks, code, search, or automation. The site manifest includes the modules you select and the dependencies they require. If a needed service is missing, package its repository with the same module contract instead of burying another custom install in the host.
Files and editing with Seafile and OnlyOffice; Matrix chat and calls; planning in Planka; mail and calendars in SOGo.
Prometheus metrics, Grafana dashboards, Alertmanager routing, Loki logs, Kopia backups, and ntfy notifications.
Git hosting and CI with Forgejo, Jupyter notebooks and shared hubs, disposable workspaces, and full-stack test runners.
Private metasearch with SearXNG, operator-managed per-user n8n through Workload Spawner, specialist APIs, and site-only modules.
Examples from a running test deployment
Monitoring, automation, alerts, team response, publishing, and search are separate products here, but they share one operator-controlled stack. These screenshots show the current alpha in useful, populated states rather than login pages or empty dashboards.
One dashboard combines 36 instruments from four sources, highlights the day’s largest moves, and makes stale or failed feeds obvious.
Workload Spawner’s first template creates a persistent n8n service for one user. Here it checks 18 services, sends an ntfy alert, opens the Element room, and writes an audit record.
The private topic carries specific events: 18 services healthy, 84.7 GB backed up, market data refreshed, and Friday’s restore drill assigned.
Alice and Bob close a cutover in one Matrix thread, with the health result, restored snapshot, rollback decision, and next drill all visible.
The same stack can publish its own updates: cutover complete, 18 services healthy, backup checked, and the next restore drill scheduled.
The active rootless SearXNG module serves browser and JSON search behind the shared Caddy and Keycloak edge. This query gathers results from multiple external engines.
A good fit—and when it is not
It earns its maintenance cost when you need control, reproducibility, and workflows that cross product boundaries. If one hosted product already covers the job, use the simpler product.
Sketch the first stack
Write down who will use the system, what they need to do from start to finish, which services or data must remain, and the constraints that matter. That is enough to choose a first set of modules and see where custom work is actually needed.
A useful first pass can be six lines. It does not need to name every container.