Skip to content

The app concept

In Flui, the app is the unit you deploy, observe, scale and retire. Every CLI command under flui app * and every Apps page in the dashboard works on an app. An app is more than a running process or a single piece of cluster infrastructure. It bundles the name and slug, source, runtime configuration, endpoints, certificates, builds, releases, logs and metrics that describe one deployable thing and everything around it during its life on the cluster.

This chapter defines the concept. The manifest that describes an app for Flui has its own chapter (The flui.yaml manifest), and so does the difference between Application and CatalogApp (Catalog vs application).

What an app carries

Wherever it comes from, an app carries the same set of things:

  • A name and a slug. A stable slug unique within the installation and a readable name. The slug is what every other surface refers to.
  • A cluster. Every app lives on one cluster at a time: the cluster whose dashboard you opened, or the one you pointed the CLI at. Moving it to another cluster is one operation; see Moving an app to another cluster below.
  • A source. Where the workload comes from. There are three: a container image pulled from a registry, a source repository that Flui builds for you against a flui.yaml manifest, or an entry picked from the Flui catalog of ready-made building blocks. The source decides what happens before the workload runs. The rest of the app is the same whichever source it has.
  • A runtime configuration. Replica count and scaling limits, healthcheck, ports and volumes: how the workload runs once it is up.
  • An endpoint, or none at all. An app can declare that it should be reachable from outside the cluster. The deploy flow then gives it a hostname and a matching TLS certificate. An app that does not need to be reached, such as a background worker or a job, runs without one. Endpoints are covered in DNS as part of the deploy contract. Whether the app is public on its own domain or internal behind the dashboard is its exposure, described below.
  • A release history. Every deploy and every rollback is recorded as a new, append-only release entry. The current release is a pointer into that history.

The sections below cover the points that need more than a line: resources, configuration and secrets, moving, removal, scaling and observability.

Resources: CPU and memory

Every app declares two numbers per instance: the request, which the cluster reserves and guarantees, and the limit, the ceiling the instance cannot go past. The scheduler places an instance only if the node can honour the request, and the runtime enforces the limit at peak.

The dashboard shows both numbers next to current usage. It flags the app when the request is much higher than what the workload uses (over-allocated, paying for idle capacity) or when usage runs close to the limit (under-allocated, at risk of being killed), so you see these signals without going looking for them.

When an app is killed for running out of memory, Flui writes a diagnosis and proposes a higher limit, with the reason. It never applies it on its own. Changing what an app may use restarts it and may need room the cluster does not have, so a person decides, from the dashboard, the CLI or an agent that was asked to.

The capacity gate

Before an app is installed from the catalog, Flui adds up the resource needs of the app, and of every component it brings if it is a composed stack. If the cluster does not have room, keeping roughly a 10% safety margin in reserve, Flui refuses the install up front. The refusal names the shortfall: how much CPU and memory the install needs and how much is free.

The gate treats CPU and memory differently:

  • CPU is gated on requests. CPU is compressible: oversubscribing it only throttles a workload and does not kill it. So the gate only needs the scheduling floor (the request) to fit.
  • Memory is gated on limits. Memory is incompressible: an instance holds the RAM it touches up to its limit, and a node that runs past what it can allocate starts killing workloads. So the gate uses the peak (the limit, falling back to the request when a component declares no limit).

The dashboard deploy wizard runs the same calculation, so the two surfaces agree on what will fit.

A cluster that may grow on its own changes the answer. Where a scaling group on the cluster is set to buy by itself, and one of its machines would hold the install, the gate does not refuse. The install goes ahead and waits for that machine to join.

Volumes and storage

An app that needs to keep data across restarts declares one or more volumes, each with a name, a mount path inside the workload and a size. The cluster’s storage configuration decides where the data lives. On the cluster’s shared storage, any node can reach it and the app can move freely. On a node’s local disk, access is faster but the app is pinned to that node. That choice affects placement and what happens during cluster operations such as a node resize. See Storage classes and dedicated placement.

Volumes are also why removing an app needs care. See Removing an app takes its data.

Port protocol

Every app’s main port has a protocol, http or tcp, with http as the default. The field controls observability. An http app is scraped for platform metrics such as request rates. A tcp app (a database, a message broker, anything that does not speak HTTP on its port) is not HTTP-scraped, because there is nothing useful to scrape and probing it would only add noise. Declaring tcp tells Flui the port speaks a wire protocol, not HTTP.

Readiness probes can also carry HTTP headers when the plain path-and-port check is not enough. The usual case is a Host: header, so an app that only answers for a specific virtual host still passes its health check during a rolling restart instead of failing on a request its router does not recognise.

How an app is reached

An app that has an endpoint is reached in one of two ways:

  • Public. The app gets a public endpoint: a hostname, a TLS certificate, and routing from the internet to the app. This is the default and the right choice for anything meant to be reached from outside.
  • Internal. The app gets only a cluster-internal address, with no public hostname, no certificate and no DNS record to manage. It is reachable only through the dashboard’s authenticating proxy. Use it for an admin tool or a console you want behind the platform’s own login rather than on the public internet.

Apps the catalog marks privatizable can choose between the two at install time. Others keep whatever their manifest declares.

In a third case you have no choice: a building block installed from the catalog (a database, a cache, a queue) is reachable only from inside its own cluster. It has no public hostname and no proxy route at all, because the thing that reads it is another app on the same cluster and nothing else should reach it. To reach one from your own machine, Flui opens a tunnel for you (Open a database from your machine).

Use the URL the app gives you

Each app’s API representation carries the links it should be reached by. A public app exposes a url of the form https://<fqdn><entrypointPath>; an internal app exposes an internalUrl instead. Anything that uses these links (the dashboard’s “Open” button, the assistant) must use the value exactly as given and must not build a URL from the slug. The slug is not the hostname. The stored url / internalUrl accounts for the endpoint’s actual domain and path, and only that value is guaranteed to be correct.

A URL is also published only once the endpoint serves, reconciled and with a valid certificate. Before that the app shows no URL at all, so you are never sent to a link that ends in a browser error.

Configuration and secrets

Plain configuration reaches the workload as environment variables and lives with the app. Sensitive values use the same name-value shape but are stored encrypted and write-only: the surfaces show the names so you know what is set, and never the values. You can overwrite a sensitive value at any time, but you cannot read it back once stored.

Three sources can set those values, and the order between them is fixed.

wins Override at deploy --env or --env-file on flui deploy wins over both flui.yaml the keys it declares, reclaimed on every deploy wins over a hand-set value Set by hand dashboard or flui app env set kept on keys the manifest does not declare Linked value a key that points at a building block's own credential, such as its database password. A plain value never overwrites it.
Which value an application gets: an override at deploy beats flui.yaml, which beats a value set by hand; a linked credential is never overwritten.

The manifest wins for the keys it declares. Every deploy lets the manifest take back every key it declares. A value set by hand only survives on a key the manifest does not declare. When the manifest overwrites a pinned value, the deploy reports it.

Explicit overrides win over both. A value passed at deploy time with --env or --env-file beats the manifest and the stored value alike.

A linked value is never overwritten. When a key points at a building block’s own credential, such as the password of the database Flui installed for you, a plain manifest value does not replace it. Swapping a live reference for a copied literal is how a credential ends up somewhere nobody remembers to rotate.

Two further rules follow.

“Declared but not resolved” is not “removed”. A key the manifest declares but gets its value from somewhere else (from a person, from another service) keeps whatever is stored for it. Only a key the manifest stops declaring entirely is treated as a removal.

“Awaiting a value” is a real state. A sensitive key that has been declared but never supplied is listed with the others, labelled awaiting a value. It is not blank, missing or an error, and listing keys does not fail because one is outstanding. flui app env list shows the three states side by side: plain values, which sensitive keys are set, and which are still waiting. flui app env set <app> <KEY> asks for the value instead of taking it as an argument, so it never lands in your shell history.

This matters most when an agent is involved. An agent can declare that a key is needed but cannot supply one. The pending state lets it hand the job to you and stop, instead of inventing a placeholder.

A changed value does not reach a running instance by itself. The app receives it on its next deploy or restart, and the CLI tells you so when you set one.

The whole flow, on all three surfaces, is in Set an application’s configuration and secrets.

How an app is classified

The dashboard groups apps along two independent axes:

  • Category. Two values: system and user. System apps are the components of the platform itself (the API, the dashboard, the identity provider, the database, the observability stack), installed and reconciled by Flui as part of the control cluster. User apps are everything you deploy. You operate both through the same surfaces. The difference is that system apps are protected from accidental deletion: a delete command refuses unless forced.
  • Kind. A broader grouping by purpose: database, tool, application, system. The dashboard uses it to sort apps into three lists: Applications (tools included), Data and System. It does not change the lifecycle.

Both are separate from the manifest’s own kind: field (Application vs CatalogApp), which describes where the app comes from, not what it does. See Catalog vs application.

The lifecycle

An app is in one of a few steady states:

  • Awaiting build: for apps built from source, while the build is running and the platform is waiting for an image.
  • Running: the app is up and serving its work.
  • Waiting for room: deployed, but some copies have no node with room to run on. They start as soon as room appears, for example when a scaling group adds a machine.
  • Degraded: the app is up but not at full health (some replicas down, a healthcheck failing intermittently).
  • Stopped: scaled to zero; the configuration is still there, and no instances are running.
  • Failed: a terminal failure (the last build failed, the last deploy failed, or an unrecoverable condition was hit).
  • Deleted: the app has been torn down; the row remains for audit.

During an operation such as a deploy, a rollback or a teardown, the app briefly passes through transient states that the dashboard shows as progress indicators rather than steady values.

Operations

The everyday operations on an app are:

  • Deploy / redeploy. Apply (or apply again) the app’s configuration. For image-based apps a redeploy can also pin a different image version. For source-built apps a redeploy reuses an existing build instead of building again. Every deploy adds a new release entry.
  • Rollback. Move the current release pointer back to a previous entry. This adds a new release entry (marked as a rollback) instead of rewinding history, so the record of what happened stays intact. Rollback is a dashboard action. From the CLI, flui app redeploy <app> <tag> rolls out an older image without rebuilding.
  • Start / stop. Bring the app’s replicas back from zero, or scale them to zero while keeping the configuration in place. Stopping can be reversed without redeploying.
  • Restart. Replace the running instances without changing the release. See the rolling restart section below.
  • Scale. Set the replica count explicitly.
  • Move. Take the app to another cluster in one operation.
  • Delete. Remove the app from the cluster, together with the data it was keeping. Read the removal section below before you run it. System apps require an explicit force flag.

Rolling restart

When the running instances of an app have to be replaced, to pick up new configuration or secrets or to clear a stuck state, Flui rolls them: new instances come up first, and the old ones go away only once the new ones are healthy. The replica count never drops below the declared number, so the app keeps serving throughout. The platform also starts a rolling restart on its own whenever a change it can apply in place needs to reach the running instances.

Surviving the loss of a node

When an app runs two or more copies, Flui spreads them across the cluster’s nodes where there is room, so losing one node does not take every copy with it. A dedicated app is not spread: it stays on one node.

flui app status says whether the app survives losing a worker and, when it does not, lists every reason with what to change: a single copy, copies that all run on one node, a volume that stays on one node, a dedicated placement, an address that names one node, or only one node taking traffic for its names. Agents get the same answer from the app_availability tool.

Moving an app to another cluster

A move is one operation with a cutover in the middle. Flui builds the copy on the destination cluster while the source keeps serving. When the copy is ready, the app is rebound to the destination and its address follows it. You can let that happen as soon as the copy is ready, or hold it and cut over yourself while you watch.

Two things hold throughout:

  • Before the cutover, nothing on the source has changed. Aborting tears the destination down and leaves the original exactly as it was.
  • After the cutover, the source workload is still there, drained. Destroying it is a separate, irreversible step that you take yourself.

Moves that cannot work are refused up front. A move of the app alone is for an app that holds no data of its own: an app with volumes, an app pinned to a dedicated node, or an app wired to another app’s credential is refused with a message saying which. Data moves through its own operation, and an app together with its database moves through a third that cuts both over at once. See Move an application to another cluster.

Removing an app takes its data

Removing an app removes the storage it was using. A volume does not outlive the app that made it, so nothing stays on the cluster unlisted while you keep paying for it.

Because this cannot be undone, every surface shows what will be lost before it asks. The confirmation names the volumes and how much data they hold. For a composed install it names the whole install, because the removal takes all of it.

When Flui cannot read the storage, for example because the cluster is unreachable or it cannot open the credentials, it reports the loss as unknown, never as none. It does not print “no data will be lost” based on a failed read. When you see unknown, assume there is data.

flui cluster volumes lists the volumes nothing mounts and no app owns, with a way to remove them one at a time. The scan can miss volumes: it only reports a volume when nothing mounts it and no existing app owns it, so a volume created outside Flui and carrying none of its labels never appears in it.

The removal flow on each surface is in Remove an application and its data.

Automatic scaling

An app carries a scaling setting. When it is on, Flui applies an autoscaler beside the app on every deploy. It watches the app’s own resource use and moves the replica count between the app’s declared floor and ceiling, which are 1 and 5 when the app declares neither. It watches what the app asked for: CPU utilisation, memory utilisation, or both, given as target percentages. An app can also set how fast it scales in each direction (how many instances at a time, and how long the autoscaler must be sure before it acts), which stops a spiky workload from scaling up and down constantly.

The percentages are measured against what the app reserves, not against its limit. An app that reserves 100 MiB and scales at 80% memory adds a copy at 80 MiB, well before it gets near a limit of 2 GiB. That is why an accurate reservation matters: it is what the autoscaler measures against. CPU is usually the better signal for load, because memory held after a peak rarely comes back and copies scaled on memory tend to stay.

When a new copy has nowhere to run, it waits, and that waiting is what a scaling group watches for. The cluster buys a machine, the copy starts on it, and once the load has gone the machine is given back.

Two limits apply:

  • Building blocks are not autoscaled. A database or a queue installed from the catalog runs as a single stateful workload, and no autoscaler is attached to it. Copying a database does not scale it.
  • You can also set a count by hand. flui app scale sets an explicit replica count, and stopping an app is the same operation at zero.

A cluster buying more machines is a separate decision, with its own written consent and its own ceilings in money; see What a cluster may buy for itself. App autoscaling changes how many copies of your app run on the machines you have. A scaling group decides whether to add a machine.

Logs and metrics

Every app has the same observability surfaces from the start, in both the dashboard and the CLI:

  • Logs. Streamed from every instance, searchable by level and free text.
  • Metrics. CPU, memory, network and replica counts over time: the baseline Flui collects for every app it manages, with no setup on the app side.
  • Live status. Replicas ready, current release, health at a glance.
  • Crashes. A summary of recent crashes: exit codes, out-of-memory events, restart counts, and the last log lines before each crash.

All four read from the same central backend on the control cluster. See Observability is not an afterthought.

Going deeper when something is wrong

When the everyday views are not enough (a first release that won’t start, a sudden burst of crashes, a configuration that breaks the workload in a subtle way), the dashboard has two deeper tools under an Advanced menu on every app:

  • Diagnoses. An automated read of recent failures that goes further than the crash list. It correlates exit codes, out-of-memory events, healthcheck failures and the log lines around them, and suggests a probable cause and a fix (a missing environment variable to add, a memory limit to raise). Unresolved diagnoses also appear as a banner on the app’s overview.
  • A per-instance deep read. An on-demand snapshot of every running instance of the app, with the low-level detail you need when the higher-level views fall short. It refreshes on request rather than continuously, so the cluster does not pay for a constant detailed read of every app.

These tools are for when logs, metrics, status and crashes are no longer enough. They are not part of the routine flow.

System apps are apps

The platform’s own components (the API, the dashboard, the identity provider, the database, the observability stack) are apps in this model too. They appear in the app list, have releases, expose logs and metrics, and go through the same lifecycle as user apps. The only difference is the system category and its protection: their delete operation refuses unless you force it.

So you do not learn one set of commands for Flui’s own components and another for your apps. You read the dashboard’s logs the same way you read your app’s logs.

Where this chapter goes from here