Alpha · reproducible self-hosted infrastructure

Stacks should compile.

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.

What Platform Zero actually does

Turn one site manifest into a host-ready Podman bundle.

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

Shared code stays reusable. Each deployment keeps its own details.

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.

01 / shared engine

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/
02 / one service

Modules

Package one service or a related group: its runtime contract, configuration, container context, dependencies, and module-owned tests.

stack.runtime.yaml + config + tests
03 / one deployment

Site config

Selects modules, pins their Git revisions, supplies deployment-specific configuration, and identifies the target environment.

selection + pins + site values
  1. 1

    Select

    List the modules you want in one flat manifest and check out the exact revisions beside the generator.

  2. 2

    Generate

    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
  3. 3

    Validate

    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
  4. 4

    Activate

    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

Per-user boundaries are a good fit for Podman.

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.

Typical shared Docker layout

Often one socket, one daemon, and many workloads.

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.

Platform Zero + Podman

Separate OS users for separate trust domains.

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.

01 / contain control

One socket does not automatically grant stack-wide control.

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.

02 / remove the middle

Systemd manages the lifecycle directly.

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.

03 / add APIs narrowly

API-compatible sockets stay scoped to their owners.

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

Most services run in rootless Podman domains. Host access is an explicit exception.

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.

01 / placement

Most applications do not run as root.

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
02 / isolation

Workload managers get their own Podman sockets.

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
03 / identity

Single sign-on stops at a defined edge.

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
04 / secrets

Plaintext secrets stay out of the generated bundle.

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
05 / preflight

Validation does not have to change the host.

Without --activate, the installer checks the bundle manifest, environment files, Quadlets, and generated units, then exits before installation.

validate first → activate deliberately
06 / recovery

Activation and updates keep rollback paths.

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

Pick the services your workflow needs—and leave the rest out.

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.

Examples from a running test deployment

A few cross-service workflows in the current test stack.

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.

A good fit—and when it is not

Platform Zero is for teams running several self-hosted services together.

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.

Platform Zero fits when

  • You need several open-source applications under one identity and deployment model.
  • A real workflow crosses tools—for example, a health check triggers automation, an alert, and a team response room.
  • You need exact source pins, inspectable runtime output, validate-first activation, and a rollback path.
  • You maintain private services without wanting to fork the shared generator.

Choose something simpler when

  • One hosted application already handles the complete workflow.
  • No one on the team can own operating-system updates, backups, restores, and service upgrades.
  • The integrations would create more failure modes than manual work they remove.

Sketch the first stack

Describe what people need to do, from start to finish.

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.

Stack sketch

A useful first pass can be six lines. It does not need to name every container.

Read the generator source