Uninstall
Three scopes, from the smallest.
One module
Section titled “One module”Set kube.<module>.enabled = false and apply
(Enable a module). Turn crossplane off
last: the roles it created for other modules stay otherwise.
The socle, keeping the cluster
Section titled “The socle, keeping the cluster”-
Wait for every module’s
ResourceSetto be Ready, if you just turned one on. -
Destroy the bootstrap module:
Terminal window tofu destroy -target=module.socle.module.socle
flux-system is then left with no ResourceSet, FluxInstance or
Deployment, and no module namespace remains (28 s on floci, 2026-09-30).
Under the hood
The releases go in reverse: the envelope, the Flux instance, the operator,
then on aws the EKS add-ons and CoreDNS, and on aws and azure Cilium. The
socle Kustomization’s deletionPolicy: WaitForTermination holds the
envelope until every catalog ResourceSet is finalized, so the operator is
never removed with work left
(Uninstall order). The
operator uninstalls from the inventory of its last completed reconcile,
hence step 1. CI asserts the empty result on every run (the destroyed
suite).
Everything
Section titled “Everything”On GCP, the cluster carries deletion protection: set
gcp.deletion_protection = false and apply first. Then, on both:
tofu destroyOn GCP a first destroy fails on the certificate map, the regional
certificate and the proxy-only subnetwork: GKE removes the Gateways’ load
balancers after the cluster. Run tofu destroy again a few minutes later
(measured on a sandbox, 2026-10-07).
The releases go before the cluster, which needs the API. If the runner can no
longer reach it (on AWS it left cluster_endpoint_public_access_cidrs, say),
drop the releases from the state first:
tofu state rm module.socle.module.socletofu destroyNothing then deletes the module access Crossplane created: see the table below.
What is left behind, by design
Section titled “What is left behind, by design”| What | Why | How to remove it |
|---|---|---|
| The Gateway API CRDs | deleting a CRD deletes every Gateway and route | by hand once unused, or with the cluster |
The CRDs of external_secrets, keda, velero, crossplane, argocd_image_updater | they hold your objects | as above |
Module roles (AWS), when crossplane went first or the bootstrap left the state | nothing is left to delete them | delete the roles under /socle/<cluster>/ in IAM |
Module bindings (GCP), when crossplane went first or the bootstrap left the state | nothing is left to delete them; a socle created in the project next inherits them | remove the principal://… members of the project’s IAM policy |
| Velero’s bucket (S3 on AWS, GCS on GCP) | Crossplane may not delete a bucket of data | in the cloud, once its backups are not needed |
A pending Velero Restore | its finalizer holds the namespace | delete the Restore (Restore a backup) |
ImageUpdater objects in argocd, once argocd_image_updater is off | their finalizer holds them, and the argocd namespace, with no controller to release it | delete them before turning the module off, or release them: kubectl -n argocd patch imageupdater <name> --type merge -p '{"metadata":{"finalizers":[]}}' |
| A mirror’s pull secret | flux-system outlives the operator’s release | with the cluster |
On aws, /aws/eks/<cluster>/cluster, when control-plane logs were on and the destroy was interrupted during its five-minute wait, or EKS delivered later than that | EKS recreates the group when it delivers, with no retention and no key (AWS-20); the next apply of the same cluster_name fails on ResourceAlreadyExistsException | aws logs delete-log-group --log-group-name /aws/eks/<cluster>/cluster, or keep it: tofu import 'module.foundations.aws_cloudwatch_log_group.cluster' /aws/eks/<cluster>/cluster |
| On gcp, the cluster’s flow, NAT and GKE logs | they are in the project’s _Default bucket, which the socle does not own (GCP-17) | they expire with the bucket’s retention, 30 days by default |