Skip to content
You are reading dev: the documentation of main. The last release, 0.1.0, predates this site.

The Flux catalog

The catalog is the part of the socle that runs in the cluster: one Flux Operator ResourceSet per module, shipped in the signed artifact, rendered from the inputs OpenTofu validated. What you write is in Configure a cluster; the rules a module follows are in the Catalog module standard.

RoleOwnerHolds
InputsOpenTofu, in your rootcloud, cluster identity, socle version, kube
Templatesthe artifact, ghcr.io/do-now-io/socle/flux-modulesone ResourceSet per module, one overlay per cloud
Renderingthe Flux Operator, in the clusterreads the inputs, renders, reconciles, garbage-collects

OpenTofu knows the catalog only as a schema, the artifact knows no client, and the operator joins them: no client repository, no templating in OpenTofu.

opentofu/bootstrap applies, in order:

#Release or resourceWhereWhy it is here
1ciliumaws, azurethe cluster has no CNI (Cilium before Flux)
2corednsawsremoving the VPC CNI removed CoreDNS too
3EKS add-ons: Pod Identity Agent, snapshot controller, EBS CSI, EFS CSI (off by default)awsthe agent must run before any module asks for an identity
4flux-operatorevery cloudpinned exactly (operator_version, 0.60.0)
5flux-instanceevery cloudthe controllers, no sync block, built from the operator’s embedded distribution (SOCLE-33)
6socleevery cloudthe envelope, two objects below
  • ResourceSetInputProvider flux-system/socle: your configuration, as OpenTofu validated and normalised it. Every module reads this one provider.
  • ResourceSet flux-system/socle-root: renders the OCIRepository socle (URL, tag, optional pull secret, cosign verify) and the Kustomization socle on ./clusters/<cloud>. It is Ready only when every module is: that, not a green apply, is convergence.
Under the hood

The envelope is a local chart, manifests/, whose only Helm expression is toYaml .Values.inputs; the << >> delimiters are the operator’s. socle-root uses resourcesTemplate, a raw string, because the optional secretRef adds or drops a whole key. The socle Kustomization has prune: true, wait: true and deletionPolicy: WaitForTermination. The envelope’s chart version equals VERSION: the helm provider re-applies a local chart only when its version or values change.

What the inputs hold (defaultValues, the bootstrap’s inputs output):

PathWhat it is
cloudaws, gcp, azure or scaleway; picks ./clusters/<cloud>
cluster.name, .environment, .owner, .region, .accountIdthe cluster’s identity; accountId on aws only
cluster.projectId, .projectNumberthe GCP project the cluster runs in, on gcp only: a module’s Workload Identity principal names its number and the pool named after its id, and a bucket’s name carries the number
socle.url, .version, .pullSecretwhere the artifact is pulled from
cosign.issuer, .subjectthe identity the signature must match
modules.<module>.<attribute>every module, every attribute, your values over the defaults
cilium.installed, .gatewayApi, .hubblewhat the bootstrap decided about the network
gateway.className, .shared, .namespace, .certificateArnthe GatewayClass, whether the shared Gateways exist, their certificate on aws
gateway.certificateMap, .regionalCertificateon gcp only: the Certificate Manager map of the public Gateway, and the certificate of the private one
storage.snapshotsthe CSI snapshot controller and its CRDs are there: on aws from the add-on the bootstrap installed, on gcp always (GKE manages the PD CSI driver and the snapshot CRDs)

The schema is catalog.tf: kube is any, validated against it at plan, then merged over the defaults, so every attribute exists and templates test values, never presence. What Flux deploys per cloud is declared again in oci/clusters/<cloud>/kustomization.yaml; check-catalog-clouds.sh fails CI when the two disagree.

Each rule was measured on a live cluster; the checklist is the Catalog module standard.

  • One provider: inputsFrom the ResourceSetInputProvider socle, never inline inputs.
  • The toggle is on each resource: fluxcd.controlplane.io/reconcile: << if inputs.modules.<m>.enabled >>enabled<< else >>disabled<< end >> on every object’s own annotations. In commonMetadata it is not templated.
  • What is a cloud’s, that cloud adds: the module in oci/catalog/ never reads inputs.cloud and is written as steps, namespace then workload; a DNS provider, a credential, a role, a Gateway’s shape is oci/clusters/<cloud>/<module>/patch.yaml, which inserts a step cloud between them or appends one (SOCLE-32). Never from OpenTofu (hello shows it).
  • A module carries its own cloud access as Crossplane resources (Module IAM).
  • Deleting a ResourceSet uninstalls everything it rendered.

The HelmRelease has no inline values:; its valuesFrom reads, in order:

  1. <module>-socle-values, a ConfigMap with the socle’s defaults;
  2. <module>-cloud-values, a ConfigMap with the cloud’s, from the cloud’s step, when it has values for the chart (optional: true);
  3. <module>-client-values, a ConfigMap from kube.<module>.values;
  4. the Secret named in values_secret, optional: true.

Later entries win, so yours beat the socle’s, and values beats a named attribute on the same key. Secrets are refused in values, which reaches the state and a ConfigMap.

Under the hood

helm-controller merges spec.values over valuesFrom, so a socle default written inline would beat you; moved into the first reference, it loses. Every ConfigMap carries the module’s toggle and reconcile.fluxcd.io/watch: Enabled, so a change reconciles at once. An overlay cannot JSON-patch one value inside a values document, so the cloud’s values are a ConfigMap of their own, rendered by the step the cloud adds before the chart’s.

Turning a module off garbage-collects what it rendered. Its CRDs:

ModuleCRDs when offWhy
gateway_apikeptdeletionPolicy: Orphan: deleting a CRD deletes every Gateway and route
external_secretskepthelm.sh/resource-policy: keep: your ExternalSecrets and stores outlive it
kedakepthelm.sh/resource-policy: keep: your ScaledObjects outlive it
velerokeptcleanUpCRDs: false: a re-enable re-syncs Backups from the bucket
argocd_image_updaterkepthelm.sh/resource-policy: keep: your ImageUpdater objects outlive it, inert; delete them first, their finalizer needs the controller
crossplanekeptthe core applies them outside Helm; module roles still declared are orphaned in the cloud
kyvernodeletedthe chart’s default: every Kyverno policy goes with them

The same holds for a full Uninstall.

tofu destroy removes the envelope, then the instance, then the operator. deletionPolicy: WaitForTermination on the socle Kustomization holds the envelope until every catalog ResourceSet is gone; without it, five survived with their finalizer on CI’s first destroy. The procedure is in Uninstall.

  • 2026-09-21, floci 1.5.34, flux-operator 0.60.0, Flux 2.9.5: apply 62 s; a value change rendered in 5 s; a module off garbage-collected in 10 s; second plan empty.
  • 2026-09-30, e2e.yaml, floci 2.1.0: socle-root Ready 46 to 56 s after Chainsaw starts; tofu destroy -target=module.socle leaves nothing, in 28 s.