Composed catalog apps
Some software is more than a single container. Nextcloud needs a
database and a cache, plus an optional document server. WordPress
needs a database next to its web tier. Immich needs several
services that only make sense together. A composed catalog app
is a single CatalogApp manifest that bundles all of those
workloads and installs them as one unit. They are provisioned
together, removed together, and shown as one entry in your app
list.
This chapter covers how a composed entry is put together, how its
pieces are wired, and the choices it offers at install time. The
broader difference between Application and CatalogApp is in
Catalog vs application.
What a composed app is
A composed entry sets type: composed (CatalogSpecComposed in
the schema). Instead of the single image, ports and env body
of a standalone entry, it carries a components list. Each
component becomes its own running app, with its own workload, its
own internal address and its own storage volume if it declares
one, as if it had been installed on its own. The composed manifest
coordinates them: it decides what gets created, in what order, and
how the pieces find each other.
You do not install a database, then a cache, then the app, and then wire three sets of credentials by hand. You pick one entry, answer a few prompts, and the whole stack comes up wired together. If a required component fails, the install stops and is marked failed. Running it again picks up where it stopped (see Idempotency below).
The component model
Each entry under components has a name, an image, and the
same runtime blocks a standalone app has (ports, volumes,
env, resources, scaling, healthcheck). Two extra fields
coordinate the stack:
dependsOn: names of other components that must be healthy before this one starts. The installer brings components up in dependency order, so a web tier markeddependsOn: [db]waits for the database to pass its healthcheck first.when.option: includes the component only when a named install-time option is enabled. A component without awhenis always included.
Components reference each other through templating in their env.
Here a web component reads its database host and credentials from
the db component:
components: - name: db image: registry: docker.io repository: library/mariadb tag: "11" env: - name: MARIADB_USER value: wordpress - name: MARIADB_PASSWORD valueFrom: generate: secret length: 32 # ...ports, volumes, resources, scaling, healthcheck
- name: web dependsOn: - db image: registry: docker.io repository: library/wordpress tag: "6.5-apache" env: - name: WORDPRESS_DB_HOST value: "{{components.db.host}}:3306" - name: WORDPRESS_DB_PASSWORD value: "{{components.db.env.MARIADB_PASSWORD}}" secret: true # ...ports, volumes, resources, scaling, healthcheckThe {{components.db.host}} and {{components.db.env.*}} tokens
resolve at install time to the database’s internal address
and the value the database generated. The generated password is
created once on the db side and injected into web as a secret
(secret: true), so it never appears in plaintext.
Auth modes
A composed entry declares which authentication strategies it
supports under spec.auth:
spec.auth.modesis the list of supported modes.spec.auth.defaultis the one chosen when the installer is not told otherwise.
At install time you pick one mode via the install’s authMode,
which must be one of oidc, proxy, native or none. native
leaves the app’s own login in place; none disables auth wiring;
proxy puts the app behind a header-injecting proxy; oidc wires
the app to Flui’s identity provider.
OIDC auto-provisioning
When you choose oidc, Flui sets up single sign-on for you. It
registers a web OIDC client with Flui’s own Zitadel-backed identity
provider, scoped to the install’s resolved hostname, and records
the resulting oidcAppId and oidcProjectId on the install
record. Flui uses those IDs to remove the client when you
uninstall, so an SSO registration never outlives its app.
The client’s credentials reach the app through the channel the
manifest declares under spec.auth.oidc:
envMapping: the issuer URL, client ID and client secret are injected as environment variables the app reads natively.configFile: a rendered config file is written into the container at a declared path.- A
postInstallexec step: the app is configured through its own CLI. Nextcloud, for instance, runsphp occ user_oidc:provider Fluito register Flui as an OIDC provider after the container is up.
In every case the credentials arrive through the templating tokens described below and are never hard-coded in the manifest.
Install-time options
spec.options is a list of boolean feature toggles. Each option has
a key, a human label, an optional description, and a default:
options: - key: office label: "Office editing (Collabora)" description: "Adds a document-server component." default: falseAt install you pass an options map that sets each key to
true or false. Any key you omit falls back to its default.
Options control two things:
- Components, via
when.option. Enablingofficeon Nextcloud adds the Collabora document-server component; leaving it off skips that component. - Post-install steps, via the same
when.option, so the step that configures the optional component runs only when the option is on.
This way one manifest describes both a minimal install and a full one, without splitting into two entries.
Multi-endpoint
A composed stack can expose more than one component to the
internet. Any component with a port marked expose: true gets its
own endpoint and FQDN, resolved by Flui’s endpoint-mode resolver in
the same way as a standalone app’s endpoint. Components with no
exposed port stay internal: a database or cache is reachable only
from the other components over the internal address, and is never
published.
Post-install steps
spec.postInstall is a list of actions that run after the
components are deployed and healthy. Each step is either:
http: an HTTP call (method,path, optionalheaders,body,expectStatus) against the running app.exec: a command run inside a component’s container (theocccalls above are exec steps).
Steps can be conditioned with when.authMode and when.option, so
the OIDC provider registration runs only in oidc mode and the
office configuration step runs only when office is enabled. Use
these steps for work that needs a live, configured app, such as
trusting the public hostname, registering the SSO provider or
enabling an optional feature.
Templating tokens resolved at install
Besides the cross-component references shown above, three groups of tokens are resolved at install time and are specific to composed installs:
{{self.fqdn}}: a component’s own public hostname, available before the component boots. The install slug is deterministic, so Flui knows a component’s final FQDN in advance. A component that needs to advertise its own public URL (Collabora’sserver_name, for example) gets it at create time instead of being reconfigured later.{{oidc.clientId}},{{oidc.clientSecret}},{{oidc.issuerUrl}}: the credentials of the web OIDC client Flui provisioned for this install (see above). They appear inenvMapping, in a renderedconfigFile, or in apostInstallexec command, and are filled in only whenauthModeisoidc.{{install.resolvedFqdn}},{{components.<name>.fqdn}}: the install’s public hostname and a component’s, available topostInstallsteps once the endpoints exist.
How a composed install appears
In flui app list a composed install shows up as a single
bundle of kind composed (a single app is standalone), instead
of as several unrelated apps. The bundle represents the whole
stack, so Nextcloud appears as one entry, not as an app, a
database and a cache side by side.
To see what is inside, pass -x/--expanded:
flui app list --expandedThat reveals the individual components, each with its own slug, status and exposure. Without the flag, the list stays at the bundle level and tells you a composed app has components you can expand.
Idempotency
You can safely run an install again. The installer looks up the components that already exist for the install and reuses them instead of creating duplicates. After a partial failure or a retried job, the install continues where it left off instead of creating a second copy of the stack. For the same reason, the post-install steps and the OIDC wiring are safe to reach more than once.
A note on capacity
Before a composed install starts, Flui sums the resource requests of every component that will be included and checks them against what the cluster has free. If there is not enough room (keeping a safety margin), it refuses the install. This is the same capacity check that applies to any install. How CPU and memory are counted is covered in The app concept.
Where to read next
- The flui.yaml manifest: the shared envelope, including the fields of a composed manifest.
- Catalog vs application: the three kinds of catalog entry and where composed fits.
- The app concept: what each component becomes once it is running, and the capacity check in full.
flui deploy: the command reference for installing and deploying.