MakerFLOSS/slides/tappaas/how-to/new-module/index.md
Lars Rossen f02c4f878f
All checks were successful
Build docs site / build (push) Successful in 46s
Build slides / build (push) Successful in 1m5s
slides(tappaas): participants land in the devops group
2026-08-22 18:38:19 +02:00

9 KiB

marp theme class paginate title description
true gaia invert true How to implement a TAPPaaS module Live build of a Podman module — OrangeMaker, 24 August

Getting a module onto TAPPaaS

Live build of a Podman container host

OrangeMaker · 24 August


The plan

  1. What TAPPaaS is — a few diagrams, ten minutes, no deeper
  2. What a module actually is — a json contract and three scripts
  3. Build one live — the lab1 environment, then podman into it
  4. Prove it works — tests, then the web console
  5. You get a login — your own identity on this system

You leave with: a mental model, a module you watched get built, and an account.


The shape of a TAPPaaS site

flowchart LR
  sat["satellite VPS<br/>public IP · ingress<br/>off-site backup"]
  subgraph z0["mgmt · zone mgmt"]
    cicd["tappaas-cicd"]
    net["network"]
    clu["cluster"]
    bak["backup"]
    idp["identity"]
  end
  subgraph z1["default · zone srv"]
    nc["nextcloud"]
    ha["home-assistant"]
  end
  subgraph z2["lab1 · zone lab1"]
    pod["podman — today"]
  end
  sat --> net
  cicd --> nc
  cicd --> pod

The words we will use

Word Meaning
Module The smallest deployable unit — its own VM, its own json contract. Foundation and apps are modules.
Environment A tenant. mgmt and one named after the site always exist; add more freely. Today we add lab1.
Zone A VLAN with a firewall policy. Modules land in their environment's zone.
Mothership tappaas-cicd — where the managers run and where you type.
Satellite Optional VPS, for sites with no usable public IP.

Everything is a module. There is one model to learn, not eight.


Developing modules: dev instance, private branch

flowchart RL
  subgraph Development["Development repository"]
    dev_main["main"]
  end
  subgraph Upstream["Upstream repository"]
    up_main["main"]
    up_stable["stable"]
    up_main -->|merge| up_stable
  end
  subgraph InstanceDev["TAPPaaS instance · dev"]
    localDev["clone of Upstream"]
    localDev_dev["clone of Development"]
  end
  subgraph InstanceProd["TAPPaaS instance · prod"]
    localProd["clone of Upstream"]
  end
  localProd -->|pull| up_stable
  localDev -->|pull| up_main
  localDev_dev -->|push| dev_main
  dev_main -->|PR| up_main

Our setup tonight

flowchart RL
  subgraph t["TAPPaaS · codeberg"]
    stable["stable"]
    main["main"]
  end
  subgraph c["Community · codeberg"]
    cm["main"]
  end
  subgraph f["forgejo.makerfloss.eu"]
    fm["main"]
  end
  subgraph inst["Our TAPPaaS · tappaas-cicd"]
    lt["clone of TAPPaaS"]
    lc["clone of Community"]
    lf["clone of MakerFLOSS"]
  end
  lt -->|pull| main
  lc -->|pull| cm
  lf -->|pull| fm
  classDef tracked stroke:#ff8a80,stroke-width:3px
  class main tracked

We track main, not stable — this is a lab, we want the new things.


A module is a contract and three scripts

podman/
├── podman.json     # the contract — what it is, needs, provides
├── install.sh      # put the software in the VM        (once)
├── update.sh       # keep it patched                   (on schedule)
├── test.sh         # prove it still works              (gates updates)
├── README.md       # what it is
└── INSTALL.md      # what automation cannot do for you

That is the whole surface. Everything else is the platform's job.


The contract, for real

{
  "description": "Podman container host — Debian 13 VM with rootless Podman + Cockpit",
  "vmname": "podman", "vmid": 812,
  "dependsOn": ["cluster:vm", "templates:debian", "backup:vm",
                "network:proxy", "identity:identity"],
  "config": {
    "cluster:vm": { "cores": 2, "memory": "2048", "diskSize": "20G",
                    "image": "debian-13-generic-amd64.qcow2" },
    "network:proxy": { "proxyPort": 9090, "proxyUpstreamTls": true,
                       "proxyUpstreamHttp1": true, "proxyAllowedZones": ["mgmt"] }
  }
}

dependsOn is the whole trick: five services, declared — not scripted.


The three scripts, honestly

Script Called Does
install.sh once, at add For podman: exec update.sh — install is update here
update.sh every schedule apt: podman, podman-compose, cockpit, cockpit-podman; enable the rootless socket
test.sh on demand + before every update merge VM reachable · podman present · plugin present · console answers on :9090

If test.sh is honest, unattended updates are safe. That is the entire deal.


What you did not have to write

  • A VM — cluster:vm built it from the Debian 13 cloud image
  • OS prep — templates:debian did apt + guest agent
  • A VLAN, an interface, DHCP, firewall rules — the zone came with the environment
  • https://podman.lab1.makerfloss.eu with a real certificate — network:proxy
  • A login — identity:identity, so it is your TAPPaaS account, not a local one
  • Nightly backup to PBS, scheduled updates, health reporting

A few lines of json bought all of it.


Demo 1 — an environment of our own

network-manager zone add lab1 --from-zone srv
network-manager show lab1              # vlan tag, subnet, access-to
network-manager reconcile              # dry-run: drift on all 4 planes
network-manager reconcile --apply      # converge OPNsense, Proxmox, switch, AP

environment-manager add lab1 --display "MakerFLOSS lab" \
    --zone lab1 --domain lab1.makerfloss.eu
environment-manager list
environment-manager show lab1

A tenant with its own VLAN, firewall posture and DNS names — lab1.makerfloss.eu outside, lab1.internal inside.


Demo 2 — install the module

module-manager module add podman --environment lab1

module-manager module list             # what is deployed
module-manager module show podman      # the resolved config, cascade applied

Watch the order: dependencies resolve first, then the VM, then the network, then install.sh. Install order is computed from every module's dependsOn — nobody maintains a list.


Demo 3 — prove it, then look at it

module-manager module test podman

Then open https://podman.lab1.makerfloss.eu — Cockpit, with the Podman page.

  • Reachable from the mgmt zone only. Not from the internet, by declaration.
  • You log in with your TAPPaaS identity — that is what the identity:identity dependency buys; no per-VM Linux passwords to hand out.

Where this module lives

Community/src/larsrossen/containers/podman/

Three homes for a module, all first-class:

Home For
TAPPaaS repo, by pull request modules the whole project should carry
Community repo yours, shared, no gatekeeping — today's podman
Private repo — e.g. forgejo.makerfloss.eu yours, not shared

Adding a repository is one site-manager repository add.


Your turn — identity on this TAPPaaS

people-manager user add <you> --email <you>@example.org \
    --roles user --groups devops
people-manager reconcile          # preview
people-manager reconcile --apply  # push to Authentik

authentik-manager user-recovery-link <you>   # one-time URL: set your own password

One login per person, roles via groups. Nobody types a password into a chat window.


Take it further

  • Develop a module — tappaas.org/generated/develop-a-module
  • Every json field — tappaas.org/generated/schemas
  • Zones and the access model — tappaas.org/generated/zones
  • Repository topology — tappaas.org/generated/design/cicd-git
  • This deck — slides.makerfloss.eu/tappaas/how-to/new-module

Pick an app you want on a sovereign box. Open an issue first — someone may already be packaging it.