Skip to content

The build pipeline

For an application built from source, there is a step between pushing code and a container running on the cluster: the build. Where the build runs affects the cost model, what fails when something breaks, and where credentials need to live.

This chapter covers the build pipeline that turns a kind: Application manifest and a source repository into a deployable image: what it does, what happens to the image once the build succeeds, and what the pipeline does not try to do.

Build at the source

The pipeline runs alongside your source repository, on the CI service of the platform that hosts it. When a deploy of a kind: Application is triggered, Flui writes (or refreshes) a build workflow inside your repository, makes sure the workflow has the credentials it needs to push the resulting image to a container registry, and watches the run until it completes.

The pipeline uses your CI tool’s runners, logs, reruns and interface. Flui drives it but does not host it.

Push a commit on the branch Build in the repository's CI New version recorded, not deployed first deploy Rolls out the app is not live yet deploy on push: on Rolls out with flui.yaml at that commit off (the default) Waits in Releases until someone deploys it
A build records a new version; whether it rolls out depends on whether the application is live yet and on deploy on push.

Where the image is kept

The built image goes to a container registry, and the cluster pulls it from there. An installation chooses which one when it is created.

  • Your GitHub account’s registry (GHCR). The default. Each image sits next to the repository it was built from, in the account that owns it. Pulling a private image back needs a classic GitHub token, because GitHub accepts nothing else for its container registry, so connecting GitHub through the GitHub App asks for that token as a second step.
  • The installation’s own registry. Chosen with flui env create --image-registry flui. The installation runs a registry of its own, at the same address as its API. Every application gets its own place there and its own credentials: the build can only push that application’s images and the cluster can only pull them. Connecting GitHub then needs repository access and nothing else, which makes the GitHub App a one-click setup. Images go away when their application does, each application keeps its three most recently pushed and its three most recently used versions, and an installation can cap the space one application takes.

The second option is one more component to run. It suits an installation that several people share, where nobody should hand over a token that sees every repository they own.

The installation’s own registry keeps images on a volume of the control cluster by default. Created with --image-registry-storage s3, it keeps them in a bucket instead, and then several copies of it serve builds and pulls side by side. Flui can create that bucket on Scaleway with a key that reaches nothing else, or use one of yours: see the registry commands. Either way, the dashboard and flui registry storage status show the space the images take, and administrators get an alert before it becomes a problem.

When the build succeeds

Flui treats a finished build and a running application as separate things.

When the run reports success, Flui records the image against the application, together with the commit and the branch it was built from. This always happens. flui app versions <app> shows the images available for that application and which of them is deployed.

Rolling that image out is a separate step. It happens automatically only when the application has deploy on push turned on. When it is off, which is the default, the new image sits in the list as an available version, and nothing changes on the cluster until somebody asks for it, from the application’s Releases tab or with:

flui app redeploy <app> <tag-or-digest>

Off is the default because, with it on, every commit to the branch becomes a deploy whether or not its author meant it to. That suits a hobby project but not anything someone is on call for, so the person who owns the application decides.

An application’s first deploy does not wait for this. At that point the application is not live yet, so no running version is being replaced, and waiting for a second instruction would only leave a half-created application doing nothing.

When deploy on push is on, the roll-out does more than swap the image. Flui first reads flui.yaml at the pushed commit, so the push applies what the committed file says (environment variables, port, resources) as well as the newer image.

Deploy on push is a setting on the application. Turn it on or off with flui app auto-deploy <app> --on|--off. The dashboard shows when it is on, and then disables the Releases tab’s Deploy buttons, because the next build would replace a hand-picked version.

Build arguments reach the image

Some values have to be present while the image is being built, not when the container starts. The usual case is a frontend framework that bakes a public API URL into the bundle at build time. Setting it as a runtime environment variable is too late, because the value was already compiled into the JavaScript.

The manifest has a place for those:

build:
strategy: dockerfile
dockerfile: Dockerfile
args:
NEXT_PUBLIC_API_URL: https://api.example.com

Every entry under build.args is passed to the image build as a build argument. Flui resolves them from the manifest and writes them into the generated build workflow, so they stay in step with the rest of the build description instead of being assembled by the CI run itself.

This has two consequences:

  • They are not secrets. The values land in a workflow file inside your repository and are baked into the image. Anyone who can read the repository or pull the image can read them. A credential belongs in the environment variables of the running application, marked as a secret, never here.
  • They do not vary per environment. The manifest’s per-environment overrides leave the build block out, so that one built image moves between environments instead of being rebuilt for each of them.

Why an external pipeline

Building images is heavy work: compilation, dependency downloads, layer composition, image push. Doing it on the same cluster that serves production traffic creates a noisy-neighbour problem at the worst moment. Running it outside the cluster avoids that.

The CI services Flui integrates with also provide a mature build cache that makes repeated builds fast, free build minutes that cover a typical project, and a debugging surface you already know: when a build fails, you open the run page and read the logs. Flui does not have to rebuild any of that.

The trade-offs

This choice has costs:

  • External dependency. If the CI service is down, new deploys cannot land. Running applications keep running and the observability stack stays up, but the deploy path depends on a service Flui does not control.
  • Credentials in your CI. Pushing to the registry requires a credential, and that credential lives where the workflow runs. You have to be comfortable trusting that service with it.
  • Latency floor. Running the build elsewhere sets a lower bound on how fast the deploy loop can close. For tight iteration, the build time on whichever runner the CI picks is the floor.
  • Tied to one CI service. The integration is specific to one CI service. A repository hosted elsewhere has no build path through Flui.

Where this chapter goes from here