Podman is the container foundation, Portainer CE is the web interface onto it, running as a container on the very engine it manages. This is what the separate portainer module already did, so it is retired rather than duplicated. Two sockets on purpose: the rootful one is the Docker-compatible API Portainer drives (it does not support rootless), the rootless user socket is for people who ssh in and run containers by hand — which was the original podman module's point and is worth keeping. An explicit proxyDomain lets the module publish at the environment's own domain instead of <module>.<environment-domain>, making it that environment's gateway. DESIGN.md keeps the Cockpit identity analysis: it is the reason for the change, and anyone proposing 'just put Cockpit behind forward-auth' should read it.
5.9 KiB
Podman — design notes
Primary audience: whoever changes this module next.
Why Debian and not NixOS
The default TAPPaaS module VM is NixOS. Podman ships in Debian 13 (trixie) main, as do
cockpit and cockpit-podman, so a Debian cloud image gets the whole module from apt with
no packaging work and no flake to maintain. The trade-off is that the VM's state lives in
update.sh rather than in a declarative .nix file — acceptable for a container host,
whose real content is the containers the operator runs on it.
Identity integration — the analysis
The module currently authenticates against Linux (PAM) accounts on the VM: the
cloud-init tappaas user has no password until an operator sets one by hand. That is a
per-VM credential nobody manages, and it is the one part of this module that is not
platform-integrated.
What TAPPaaS offers
The identity module (Authentik) provides two services, and USERS.md is explicit that they must never be stacked on one app — double login, no benefit:
| Mode | dependsOn |
Mechanism | In-app account |
|---|---|---|---|
| OIDC | identity:identity |
the app is an OIDC client of Authentik; groups arrive in the groups claim |
created just-in-time on first login |
| Forward-auth | identity:accessControl |
Caddy asks the Authentik outpost, then injects X-Authentik-* headers |
none — the URL is simply gated |
identity:identity writes OIDC_CLIENT_ID / OIDC_CLIENT_SECRET /
OIDC_DISCOVERY_URI into the VM's secrets env file and restarts a configure service, so
the app can register the provider. identity:accessControl creates a Proxy Provider in
Authentik, attaches it to the embedded outpost, and flips the module's existing Caddy
handler to ForwardAuth=1. Both bind the application to an allow-list of groups at install
time — mandatory, because Authentik fails open.
What Cockpit can actually consume
Cockpit is not an OIDC client, and cannot become one by configuration. Its maintainers are direct about the alternatives (cockpit#20814):
- launching
cockpit-ws --local-session=cockpit-bridgebehind an authenticating proxy — which means "there is zero authentication on that port", so:9090must be made unreachable by anything but the proxy; - writing a custom authentication command in the shape of
cockpit-session— "this is difficult -- this isn't configuration, this is real development".
And in every approach, including the proxy one, a local Unix account must still
exist for the authenticated person: cockpit-bridge runs inside a PAM session as that
user. Cockpit does not abstract that away.
So identity:identity is the wrong dependency for this module — the deck was optimistic.
There is no configuration that makes Cockpit take an OIDC login.
The three real options
1. identity:accessControl — gate the URL (recommended first step).
Authentik guards https://podman.<domain> at Caddy and only the bound groups get through;
behind it Cockpit still shows its own PAM form. Two logins, but the exposure is centrally
managed and group-driven, and it is one line in dependsOn plus whatever zone0 /
proxyDomain the environment resolves. No new machinery.
2. Authentik LDAP outpost + SSSD on the VM — one credential.
Authentik can serve LDAP; with SSSD on the Debian VM the Linux accounts are the Authentik
accounts, so Cockpit's own form takes the same username and password as everything else,
and podadmin-style hand-made users disappear. This is the honest fix for the local-account
requirement. It is not something this module should carry: TAPPaaS's identity module runs
only the embedded outpost, so an LDAP outpost plus a posix-accounts service belongs
upstream in identity (a third service alongside identity and accessControl).
3. True single sign-on — real development.
Option 1 or 2 plus a custom Cockpit auth command that trusts X-Authentik-Username, with
:9090 firewalled to the proxy only. Highest value, highest risk: header-trust auth is a
full bypass if the port is reachable any other way, and this module currently advertises
direct https://<vm-ip>:9090 access as a feature.
What was actually done (2026-08-25)
None of the three. All of them are workarounds for a console that cannot take an OIDC login, and the requirement had grown: registered people managing containers across the hosts in the zone, which Cockpit's deprecated host switcher cannot do securely either.
So Cockpit was dropped and Portainer CE became this module's GUI, with Podman still the
engine underneath it. Portainer is an OIDC client, so identity:identity — the native TAPPaaS
OIDC path — works exactly as designed, and multi-host is an agent per host rather than a
browser loading remote JavaScript. The analysis above is kept because it is the reason for
the change, and because anyone proposing "just put Cockpit behind forward-auth" should read it
first.
One thing to verify on a live system first
identity:accessControl's install-service hard-requires vmname, zone0, proxyDomain
and proxyPort in the installed config and — unlike identity:identity — does not
derive proxyDomain when it is absent. This module deliberately sets neither zone0 nor
proxyDomain, so that both come from the environment (ADR-007 P5). copy-update-json.sh
documents that install-module.sh "computes vmname/zone0/proxyDomain from the environment
file and passes them as explicit overrides", but no such computation is visible in
install-module.sh for proxyDomain. Check the resolved
/home/tappaas/config/podman-<env>.json after an install; if proxyDomain is empty,
accessControl will die, and the fix is upstream — mirror the derivation that
identity/install-service.sh already does.