Skip to content

Architecture

GitOps-driven Cluster API (CAPI) management platform. A disposable local kind cluster bootstraps Flux, provisions the self-managed management cluster through CAPI, and is deleted after a clusterctl move pivot. The management cluster then reconciles itself and all downstream infrastructure from this repository across multiple supported environments: AWS (aws), Azure (azure), GCP (gcp), local Docker (local-host), bare-metal Talos (local-talos), and an air-gapped bundle (airgap).

The operator normally runs the imperative lifecycle through the krops-toolbox container. It mounts the host engine socket, joins the kind network while the bootstrap cluster exists, and leaves no toolbox workload in the managed clusters. This packaging changes the host tool boundary, not the Flux or CAPI reconciliation architecture.

Architecture pillars

These principles guide every change to krops. A change that departs from one states why in its pull request.

One reconciliation loop, minimal abstractions

Flux is the single reconciliation loop. Every persistent change is YAML in Git, and Flux converges the clusters toward it: cluster lifecycle (Cluster API), per-cluster Flux instances (CAPI addons), and cloud resources (ACK, ASO, Config Connector) are all Kubernetes resources reconciled the same way. There is no Terraform, no state file, and no second toolchain with its own plan and apply cycle. The only imperative steps are bootstrap, pivot, and teardown, run by krops-bootstrap or the shell equivalents bootstrap.sh, pivot.sh, and teardown.sh (one script per phase); bootstrap and pivot hand control to Flux and exit.

Each abstraction layer is one more thing a human or AI agent operator must understand before changing the system, and one more place for mistakes and hallucinations. krops therefore uses each component's upstream API (Flux, CAPI, the cloud operators) directly instead of wrapping it in generators, templating layers, or in-house CRDs, and adds a layer only when it removes more complexity than it introduces.

Cloud operators on the management cluster

The cloud operators run on the management cluster: ACK for AWS, the ASO bundled with CAPZ for Azure, and Config Connector for GCP. They reconcile the per-workload-cluster cloud resources through mgmt//infrastructure/workload-resources/, so each workload's cloud resources are declared alongside the management cluster's other infrastructure.

The workload clusters run no controllers and hold no credentials: no operator deployment, no workload identity, no secrets at rest. A workload that needs cloud access gets it the same way the management cluster does, not by installing its own operator.

AWS environment (aws)

The reference environment manages AWS infrastructure through the Kubernetes API: CAPA provisions EKS workload clusters, CAPI addons deliver per-cluster Flux instances, and ACK operators (S3, RDS, IAM) on the management cluster manage the per-cluster cloud resources with static SOPS credentials. Workload clusters run no controllers and hold no credentials.

krops aws architecture

flowchart TD
    subgraph bootstrap["Bootstrap (one-time, krops-bootstrap CLI)"]
        KIND[kind cluster: mgmt, disposable]
        HELM[Helm: flux-operator + FluxInstance]
        SEC[Secrets: flux-github-pat + sops-age]
        KIND --> HELM
        KIND --> SEC
    end

    subgraph git["Git: github.com/polarsquad/krops"]
        REPO[(main branch)]
    end

    HELM -->|"sync: mgmt/aws/"| REPO

    subgraph mgmt["Management cluster (self-managed after the pivot) - Flux Kustomizations (dependsOn order)"]
        FS[flux-system root]
        CM[cert-manager]
        CO[capi-operator]
        CI[capa-identity]
        AMC[aws-managed-clusters]
        MGMT[management cluster def<br/>self-hosted via the pivot]
        CAPIS[capi-system]
        CAPAS["capa-system (SOPS creds)"]
        CAAPH[caaph-system]
        ACKC["ack-controllers (SOPS creds)<br/>ACK S3 + RDS + IAM controllers"]
        WR["workload-resources<br/>Bucket + DBInstance + reader Role CRs"]
        AWSIAM["aws-global-iam<br/>krops-reader console user"]
        KONF["konflate (SOPS token)<br/>rendered Flux PR review"]
        EUN[eu-north-1 cluster def]
        EUW[eu-west-1 cluster def]
        FA["flux-apps (SOPS pull secret)<br/>HelmChartProxy + ResourceSet"]

        FS --> CM --> CO
        CO --> CI --> AMC
        CO --> CAPIS --> CAPAS --> CAAPH --> FA
        CAPAS --> EUN
        CAPAS --> EUW
        CAPAS --> MGMT
        FS --> ACKC --> WR
        ACKC --> AWSIAM
        FS --> KONF
    end

    REPO --> FS

    subgraph aws["AWS"]
        EKS1[EKS: eu-north-1-workload<br/>x86 + ARM node pools]
        EKS2[EKS: eu-west-1-workload<br/>x86 + ARM node pools]
        B1[(S3: krops-...-eu-north-1-workload-data)]
        B2[(S3: krops-...-eu-west-1-workload-data)]
        DB1[(RDS: krops-eu-north-1-workload-db)]
        DB2[(RDS: krops-eu-west-1-workload-db)]
        RD1[IAM Role: krops-eu-north-1-workload-reader<br/>trust: account root]
        RD2[IAM Role: krops-eu-west-1-workload-reader<br/>trust: account root]
        RUSER[IAM User: krops-reader<br/>console login, assumes reader roles]
    end

    EUN -->|CAPA provisions| EKS1
    EUW -->|CAPA provisions| EKS2
    AWSIAM -->|creates| RUSER
    RUSER -.->|sts:AssumeRole| RD1
    RUSER -.->|sts:AssumeRole| RD2

    FA -->|"HelmChartProxy: flux-operator<br/>ResourceSet: FluxInstance + cluster-vars + pull secret (remote-apply)"| WF1
    FA -->|same, per region label| WF2

    subgraph wl1["Workload cluster eu-north-1"]
        WF1["Flux (sync: workload/eu-north-01)<br/>workload/base is empty today"]
    end

    subgraph wl2["Workload cluster eu-west-1"]
        WF2["Flux (sync: workload/eu-west-01)<br/>workload/base is empty today"]
    end

    WF1 --> REPO
    WF2 --> REPO
    WR -->|Bucket CR reconciled| B1
    WR -->|Bucket CR reconciled| B2
    WR -->|DBInstance CR reconciled| DB1
    WR -->|DBInstance CR reconciled| DB2
    WR -->|Role CR reconciled| RD1
    WR -->|Role CR reconciled| RD2

Reconciliation order (AWS management cluster)

Enforced with Flux dependsOn:

cert-manager ▶ capi-operator ▶ capi-system ▶ capa-system ▶ clusters (eu-north-1, eu-west-1)
                            │                            └▶ caaph-system ▶ flux-apps
                            └▶ capa-identity ▶ aws-managed-clusters
ack-controllers ▶ workload-resources
ack-controllers ▶ aws-global-iam
konflate (no dependencies)

The eu-north-1 cluster definitions include the management cluster itself (clusters/management/): after the pivot, the management cluster's own Flux instance reconciles the Cluster objects that define it. The bootstrap kind cluster no longer exists at this point; nothing runs from an operator's laptop.

PR review: konflate

The management cluster also runs a single konflate instance (mgmt/aws/infrastructure/konflate/), pointed at this repo (github://polarsquad/krops, rendering from the repo root). It renders each open PR at its merge-base and head and shows the diff of the rendered Flux output (blast radius, image changes, render failures, and danger lint) instead of the raw file diff. Results reach the PR two ways: a GitHub Actions workflow (.github/workflows/konflate.yml) runs a one-shot konflate service container on each PR push and gates the konflate / Rendered Flux diff check on a clean render, and the in-cluster instance posts the rendered summary comment and a Konflate commit status itself (write-back, outbound-only, so the local kind cluster needs no inbound reachability from GitHub). Both upsert the same marker-keyed comment. Full details (deployment, CI gate, tokens, write-back, UI access): PR review: konflate.

Reconciliation order (AWS workload clusters)

(empty: workload/base reconciles nothing since issue #346; the per-cluster
Flux instance stays installed, ready for a future application workload)

How workload apps are delivered (AWS)

  1. Each Cluster in mgmt/aws/clusters/ carries labels fluxcd: enabled and region: <region>.
  2. flux-apps matches those labels: a HelmChartProxy installs the Flux Operator on every fluxcd: enabled workload cluster, and a templated ResourceSet remote-applies the per-region Flux bootstrap bag (namespace, cluster-vars, pull secret, FluxInstance) into flux-system on each workload via its CAPI kubeconfig Secret, with inventory pruning on re-creation. cluster-vars keeps AWS_REGION, CLUSTER_NAME, and AWS_ACCOUNT_ID as the postBuild substitution channel for a future workload.
  3. The workload cluster's Flux reconciles workload/<region>-01, whose base/ overlay is intentionally empty since issue #346: the ACK controllers and the Bucket / DBInstance / reader Role CRs moved to the management cluster (mgmt/aws/infrastructure/ack-controllers/ and mgmt/aws/infrastructure/workload-resources/). The instance reconciles nothing today but is ready for a future application workload (the local-host Podinfo pattern).

See AWS authentication & IAM for how the ACK controllers authenticate, and Workload resources for what they create.

Azure environment (azure)

The azure environment mirrors aws: a disposable kind cluster bootstraps Flux, CAPZ v1.27.0 provisions an AKS management cluster (swedencentral-management), the pivot moves the management objects into it, and the management cluster's ASO (bundled with CAPZ) reconciles the workload Azure resources from mgmt/azure/infrastructure/workload-resources/.

No Azure secret exists at rest: the management cluster authenticates CAPZ and the bundled ASO with workload identity against the krops-capz User-Assigned Identity (federated to the kind cluster's Arc OIDC issuer at bootstrap, and to the management cluster's own OIDC issuer post-pivot), and workload clusters hold no credentials at all. The bundled ASO on the management cluster reconciles the workload Azure resources (VNet, storage account, PostgreSQL Flexible Server) and the management identity resources (krops-capz identity and its Federated Identity Credentials), all through the aso-credentials Secret.

See the architecture diagram in docs/azure-infra.svg and the Azure environment guide.

krops azure architecture

flowchart TD
    subgraph bootstrap["Bootstrap (one-time, krops-bootstrap CLI)"]
        KIND[kind cluster: mgmt, disposable]
        HELM[Helm: flux-operator + FluxInstance]
        SEC[Secrets: flux-github-pat + sops-age]
        KIND --> HELM
        KIND --> SEC
    end

    subgraph git["Git: github.com/polarsquad/krops"]
        REPO[(main branch)]
    end

    HELM -->|"sync: mgmt/azure/"| REPO

    subgraph mgmt["Management cluster (self-managed after the pivot) - Flux Kustomizations"]
        FS[flux-system root]
        CM[cert-manager]
        CO[capi-operator]
        CAPIS[capi-system]
        CAPZS["capz-system (CAPZ v1.27.0 + bundled ASO)"]
        CAAPH[caaph-system]
        AZID["azure-identity (secret-free)<br/>AzureClusterIdentity: WorkloadIdentity"]
        ASOWI["aso-workload-identity<br/>krops-capz + FICs + data RG"]
        WLR["workload-resources Ks<br/>VNet + storage + PostgreSQL"]
        SWEDENC["swedencentral cluster def<br/>swedencentral-management (self-hosted)<br/>swedencentral-workload"]
        FA["flux-apps (SOPS pull secret)<br/>HelmChartProxy + ResourceSet"]

        FS --> CM --> CO --> CAPIS --> CAPZS
        CAPIS --> CAAPH --> FA
        CAPZS --> AZID --> ASOWI --> WLR
        CAPZS --> SWEDENC
        AZID --> SWEDENC
    end

    REPO --> FS

    subgraph azure["Azure: swedencentral"]
        AKS[AKS: swedencentral-workload<br/>AzureASOManagedControlPlane + MachinePool]
        RG[(Resource Group: krops-swedencentral-workload-data)]
        VNET[(VNet: krops-swedencentral-workload-vnet<br/>subnet: postgres<br/>private DNS zone)]
        SA[(Storage Account: blob container 'data')]
        PSQL[(PostgreSQL Flexible Server<br/>private access, Entra-only auth)]
    end

    SWEDENC -->|CAPZ provisions| AKS
    ASOWI -->|bundled ASO creates| RG

    FA -->|"HelmChartProxy: flux-operator<br/>ResourceSet: FluxInstance + cluster-vars + pull secret (remote-apply)"| WF

    subgraph wl["Workload cluster swedencentral-workload"]
        WF["Flux (sync: workload/swedencentral-01)"]
        WCM["cert-manager Ks"]

        WF --> WCM
    end

    WF --> REPO
    WLR -->|bundled ASO reconciles| VNET
    WLR -->|bundled ASO reconciles in data RG| SA
    WLR -->|bundled ASO reconciles with private DNS| PSQL

Reconciliation order (Azure management cluster)

cert-manager ▶ capi-operator ▶ capi-system ▶ capz-system (bundled ASO)
                                           ├▶ azure-identity ▶ aso-workload-identity ▶ workload-resources
                                           ├▶ clusters (swedencentral)
                                           └▶ caaph-system ▶ flux-apps

Reconciliation order (Azure workload cluster)

cert-manager (the Azure resources moved to the management cluster in issue #559)

GCP environment (gcp)

The gcp environment mirrors azure: a disposable kind cluster bootstraps Flux, CAPG v1.13.1 provisions a GKE management cluster (europe-north1-management), the pivot moves the management objects into it, and the management cluster's Config Connector (KCC 1.159.0) reconciles the GCP workload resources from mgmt/gcp/infrastructure/workload-resources/ (issue #560: the workload cluster no longer runs its own KCC).

No GCP secret exists at rest. CAPG and the management-side Config Connector authenticate with Workload Identity Federation against the krops pool: their service accounts present the cluster's projected token through the kind provider at bootstrap and the Git-declared mgmt provider post-pivot, and the credential files are plain external_account Secrets with a subject condition restricted to exactly those two service accounts (krops-capg is the GSA they impersonate). The pivot applies the two credential Secrets to the target before clusterctl move (pivot-manifests) and pins GCP_WIF_PROVIDER=mgmt (pivot-manifest-vars), because the source-side ConfigMap merge order cannot guarantee it.

See the architecture diagram in docs/gcp-infra.svg and the GCP environment guide.

krops gcp architecture

flowchart TD
    subgraph bootstrap["Bootstrap (one-time, krops-bootstrap CLI)"]
        KIND[kind cluster: mgmt, disposable]
        WIF["wif-federate (post-kind-create-task)<br/>krops pool + kind provider + JWKS"]
        HELM[Helm: flux-operator + FluxInstance]
        KIND --> WIF
        KIND --> HELM
    end

    subgraph git["Git: github.com/polarsquad/krops"]
        REPO[(main branch)]
    end

    HELM -->|"sync: mgmt/gcp/"| REPO

    subgraph mgmt["Management cluster (self-managed after the pivot) - Flux Kustomizations"]
        FS[flux-system root]
        GCPV[gcp-vars ConfigMap]
        CM[cert-manager]
        CO[capi-operator]
        CAPIS[capi-system]
        CAPGS["capg-system (CAPG v1.13.1)<br/>WIF credential Secret, secret-free"]
        CAAPH[caaph-system]
        KCCO["kcc-operator (1.159.0, Helm chart via OCIRepository)"]
        KCC["kcc (ConfigConnector in cnrm-system)<br/>WIF credential Secret"]
        KCCI["kcc-identity (KCC-managed)<br/>krops pool + mgmt provider + GSA grants"]
        WRES["workload-resources (KCC CRs, issue #560)<br/>networking / storage / postgres / iam"]
        EUNC["europe-north1 cluster defs<br/>europe-north1-management (self-hosted)<br/>europe-north1-workload"]
        FA["flux-apps (SOPS pull secret)<br/>HelmChartProxy + ResourceSet"]

        FS --> CM --> CO --> CAPIS --> CAPGS
        CAPIS --> CAAPH --> FA
        FS --> KCCO --> KCC --> KCCI --> WRES
        CAPGS --> EUNC
    end

    REPO --> FS

    subgraph gcp["GCP: europe-north1"]
        GKE[GKE: europe-north1-management<br/>+ europe-north1-workload]
        POOL["Workload Identity Pool: krops<br/>providers: kind (bootstrap) + mgmt (Git)"]
        GSAS["krops-capg / krops-reader GSAs"]
        VPC[(VPC + PSA range: krops-europe-north1-workload-psa)]
        BUCKET[(Storage bucket: krops-&lt;number&gt;-europe-north1-workload-data)]
        SQL[(Cloud SQL: krops-europe-north1-workload-db<br/>private IP, IAM auth)]
    end

    EUNC -->|CAPG provisions| GKE
    KCCI -->|KCC creates| POOL
    KCCI -->|KCC creates| GSAS
    WRES -->|KCC reconciles as krops-capg| SQL
    WRES -->|KCC reconciles| VPC
    WRES -->|KCC reconciles| BUCKET
    FA -->|"HelmChartProxy: flux-operator<br/>ResourceSet: FluxInstance + cluster-vars + pull secret (remote-apply)"| WF

    subgraph wl["Workload cluster europe-north1-workload (GKE)"]
        WF["Flux (sync: workload/europe-north1-01)<br/>gcp-base root intentionally empty (issue #560)"]
    end

    WF --> REPO

Reconciliation order (GCP management cluster)

gcp-vars ▶ cert-manager ▶ capi-operator ▶ capi-system ▶ capg-system (GCPManaged* CRDs)
                                                        ├▶ caaph-system ▶ flux-apps
gcp-vars ▶ kcc-operator (StatefulSet Ready) ▶ kcc (cnrm-system) ▶ kcc-identity (krops pool, mgmt provider)
europe-north1 clusters (dependsOn: capg-system, gcp-vars)

Reconciliation order (GCP workload cluster)

Flux only. The gcp-base root is intentionally empty (issue #560): the
workload cloud resources reconcile on the management cluster - see the
management-cluster chain above, where workload-resources depends on
kcc-identity (networking first, then postgres after networking, iam last).

Local host environment (local-host)

The local-host environment replaces AWS with local Docker containers (CAPD) and GitHub with a local in-memory OCI registry (krops-registry:5000). It provisions a one-control-plane/one-worker workload cluster and reconciles the Podinfo demo application end to end on a single host. The per-workload Flux bag is delivered by a templated ResourceSet like the clouds, but the local-host bundle carries only the FluxInstance (no cluster-vars, no pull secret) and the per-cluster Kustomization sources from the mgmt flux-system OCIRepository.

See the architecture diagram in docs/local-host-infra.svg.

krops local-host architecture

flowchart TD
    subgraph bootstrap["Bootstrap (one-time, krops-bootstrap CLI)"]
        KIND[kind cluster: mgmt, disposable]
        REG["Local OCI Registry: krops-registry:5000"]
        HELM[Helm: flux-operator + FluxInstance]
        KIND --> HELM
        REG -->|oci://krops-registry:5000/krops| HELM
    end

    subgraph oci["Local OCI Registry: krops-registry:5000"]
        ARTIFACT["OCI Artifact: krops:latest<br/>published via mise run oci-push"]
    end

    subgraph mgmt["Management cluster (self-managed CAPD after pivot)"]
        FS[flux-system root]
        CM[cert-manager]
        CO[capi-operator]
        CAPIS[capi-system]
        CAPDS["capd-system (Docker provider)"]
        CAAPH[caaph-system]
        MGMT["management cluster def<br/>local-management (Docker)"]
        DOCKER["clusters/docker def<br/>local-workload"]
        FA["flux-apps (HelmChartProxy + ResourceSet)"]

        FS --> CM --> CO --> CAPIS --> CAPDS
        CAPIS --> CAAPH --> FA
        CAPDS --> MGMT
        CAPDS --> DOCKER
    end

    ARTIFACT --> FS

    subgraph wl["Workload cluster local-workload (Docker containers)"]
        CP[1 Control Plane container]
        WORKER[1 Worker container]
        WF["Flux (syncs workload/local-host from OCI)"]
        APP["podinfo demo application<br/>port-forward to localhost:9898"]
        WF --> APP
    end

    DOCKER -->|CAPD provisions| CP
    DOCKER -->|CAPD provisions| WORKER
    FA -->|installs Flux via HelmChartProxy| WF
    ARTIFACT --> WF

Reconciliation order (local-host)

Management cluster:

cert-manager ▶ capi-operator ▶ capi-system ▶ capd-system ▶ clusters (local-management, local-workload)
                            │                            └▶ caaph-system ▶ cni ▶ flux-apps

Workload cluster:

podinfo (HelmRelease reconciled by local workload Flux from OCI artifact)

Bare-metal Talos environment (local-talos)

The local-talos environment targets physical bare metal: a disposable kind cluster installs CAPI with the Tinkerbell infrastructure provider (CAPT pinned to fork v0.7.1) and Talos bootstrap/control-plane providers (CABPT v0.9.0, CACPPT v0.9.0). The controllers match a committed Tinkerbell Hardware object (talos-mgmt-01), PXE-boot the target machine, and bring up an immutable single-node control plane. The installer image is set with the hardware.tinkerbell.org/installer-image annotation on the Hardware CR, mirrored into status.installerImage by the CAPT fork and injected by CABPT; the committed definition pins no installer image, so without the annotation installs use the default Image Factory schematic.

Scope fence: local-talos is management-only. It owns no workload clusters and deploys no CAPI addons (Talos ships its own internal CNI). Teardown deletes the CAPI objects so CAPT releases the Hardware back to the Tinkerbell pool; the physical machine is never wiped or reclaimed.

See the architecture diagram in docs/local-talos-infra.svg.

krops local-talos architecture

flowchart TD
    subgraph bootstrap["Bootstrap (one-time, krops-bootstrap CLI)"]
        KIND[kind cluster: mgmt, disposable]
        HELM[Helm: flux-operator + FluxInstance]
        SEC[Secrets: flux-github-pat + sops-age]
        KIND --> HELM
        KIND --> SEC
    end

    subgraph git["Git: github.com/polarsquad/krops"]
        REPO[(main branch)]
    end

    HELM -->|"sync: mgmt/local-talos/"| REPO

    subgraph mgmt["Management cluster (bare metal, self-managed after pivot)"]
        FS[flux-system root]
        CM[cert-manager]
        CO[capi-operator]
        CAPIS[capi-system]
        CAPT["capt-system (Tinkerbell CAPT v0.7.1 fork)"]
        CABPT["cabpt-system (Talos bootstrap v0.9.0)"]
        CACPPT["cacppt-system (Talos control plane v0.9.0)"]
        TALOSMGMT["clusters/management<br/>talos-mgmt-01 (explicit controlPlaneRef)"]

        FS --> CM --> CO --> CAPIS
        CAPIS --> CAPT
        CAPIS --> CABPT
        CAPIS --> CACPPT
        CAPT --> TALOSMGMT
        CABPT --> TALOSMGMT
        CACPPT --> TALOSMGMT
    end

    REPO --> FS

    subgraph metal["Physical Metal (Tinkerbell Substrate)"]
        HW["Tinkerbell Hardware: talos-mgmt-01<br/>committed MAC / BMC credentials"]
        PXE["PXE network boot + Hook environment"]
        IMG["Talos installer image streamed to disk"]
        NODE["Single-node bare metal control plane<br/>static VIP / Talos API"]
    end

    TALOSMGMT -->|CAPT claims| HW
    HW --> PXE --> IMG --> NODE

Reconciliation order (local-talos)

cert-manager ▶ capi-operator ▶ capi-system ▶ capt-system (Tinkerbell CAPT) ──┐
                                           ├▶ cabpt-system (Talos bootstrap) ─┼▶ clusters/management (talos-mgmt-01)
                                           └▶ cacppt-system (Talos CP) ───────┘

Air-gapped bundle (airgap)

The air-gap bundle packages the local-host profile with Zarf for completely disconnected deployments. A connected build machine renders the krops GitOps tree, pulls required container images and charts, generates Syft SBOMs, and signs the resulting package. On the disconnected deploy host, Zarf initializes an internal container registry and proxy agent that rewrites image pulls, while Flux reconciles the offline management and workload clusters with zero external network traffic.

See the architecture diagram in docs/air-gap-infra.svg and the Air-gapped krops guide.

krops air-gap architecture

Pattern comparisons

Terraform and OpenTofu store desired state in HCL and a state file and apply it in discrete plan/apply runs; krops stores it as Kubernetes resources in Git that controllers reconcile continuously, so there is no snapshot to drift, no locks to fight or corrupt. One control plane covers infrastructure and workloads, so RBAC, policy, and audit span both, and cluster lifecycle and workload delivery form one dependency graph instead of a Terraform-built cluster that no longer manages what runs inside it.

krops ships no controller of its own: it combines Cluster API for clusters, each cloud's native operator (ACK on AWS) for cloud resources, and Flux for GitOps. If those resource APIs already say what you mean, krops does not wrap them to say it again. Least abstraction and one reconciliation loop are the pillars the pattern is held to.

Crossplane is the closer comparison, and in one use case it is now a sort-of competitor: the composition story, turning provider resources into higher-level platform APIs, is served by Flux ResourceSets templating plain Kubernetes resources. The overlap is that use case; the scope still differs. Crossplane brings its own provider packages, XRDs, and composition machinery, a second reconciliation plane to operate, while krops keeps one reconciliation loop over the upstream APIs of the components it already runs. Crossplane remains the evaluated alternative resource plane, selectable per environment instead of the native operator, never both, with Flux still delivering everything from Git. See Crossplane as a resource plane.

The management cluster is not a long-lived local kind cluster: the bootstrap kind cluster is disposable, and after the pivot the management cluster manages itself through the same GitOps flow it drives.

Pull requests are reviewed as rendered Flux diffs (blast radius, image changes, render failures) by konflate: a GitHub Actions workflow runs it on every PR push as a merge gate, and an in-cluster instance posts the summary to the PR, so what you review is byte-for-byte what reconciles.

Repository layout

.
├── .github/workflows/             Validation, Rust/toolbox CI, docs CI and
│                                  Pages deploy, the konflate PR render gate,
│                                  the nightly air-gapped bundle build, and
│                                  signed toolbox releases
├── airgap/                        Zarf air-gap bundle, image inventory, scripts
├── virtualized-e2e/               WireMock-virtualized e2e harness (#355):
│                                  lib/ shared components + one arm per
│                                  cloud (aws/ is the reference); not
│                                  Flux-reconciled, no mise task yet
├── bootstrap-rs/                  Lifecycle CLI, toolbox Dockerfile, Rust tests
├── bootstrap.toml                 Repository-owned lifecycle configuration
├── bootstrap.sh / pivot.sh /      Native shell references and fallback paths;
│   teardown.sh                    retained until both parity gates pass
├── scripts/toolbox-run.sh         Docker/Podman wrapper: the lifecycle entry
│                                  point (bootstrap, pivot, teardown)
├── tests/                         Config and Renovate coverage cross-checks
├── mkdocs.yml                     MkDocs Material config for the docs site
├── pyproject.toml / uv.lock       Python project for the docs site build
├── tools/assemble_docs.py         Assembles build/docs/ from README.md + docs/
├── website/                       Docs site assets: colour scheme CSS, CNAME
├── docs/                          Detailed documentation (linked from README.md)
├── mise.toml / mise.*.toml        Pinned toolchain (the toolbox image is
│                                  built from these pins) and the helper
│                                  task definitions (run in the toolbox with
│                                  --entrypoint mise; validate and the
│                                  podinfo port-forward stay host tasks)
├── renovate.json5                 Hosted Renovate discovery and grouping rules
├── mgmt/aws/                      Synced by the MANAGEMENT cluster's Flux
│   ├── infrastructure/           cert-manager, CAPI operator, CAPA identity,
│   │                              ACK controllers (S3, RDS, IAM) and the
│   │                              per-cluster Bucket/DBInstance/reader Role
│   │                              CRs, account-global IAM (reader console
│   │                              user), konflate (rendered Flux PR review)
│   ├── capi-providers/           capi-system, capa-system (SOPS creds),
│   │                              caaph-system
│   ├── addons/flux-apps/         Installs Flux on each workload cluster
│   │                              (HelmChartProxy + ResourceSet)
│   └── clusters/                 EKS cluster defs: eu-north-1, eu-west-1
│                                  (x86 + ARM MachinePools); eu-north-1 also
│                                  carries the self-managed management cluster
├── mgmt/local-host/              OCI-synced CAPI/CAPD local workload cluster
│   │                              and its management cluster definition
│   ├── infrastructure/           capi-operator, cert-manager
│   ├── capi-providers/           caaph-system, capd-system, capi-system
│   ├── addons/                   kindnet CNI, flux-apps
│   └── clusters/                 docker (workload), management (self-managed)
├── mgmt/local-talos/             Single-node Talos management cluster on
│   │                              bare metal via Tinkerbell (CAPT);
│   │                              GitHub-synced like mgmt/aws
│   ├── infrastructure/           capi-operator, cert-manager
│   ├── capi-providers/           cabpt-system, cacppt-system, capi-system,
│   │                              capt-system (Tinkerbell)
│   └── clusters/                 management (self-managed)
├── mgmt/azure/                    AKS management cluster (CAPZ + ASO)
├── mgmt/gcp/                      GKE management cluster (CAPG + Config
│   │                              Connector operator + WIF identities)
└── workload/                     Synced by each WORKLOAD cluster's Flux
    ├── base/                     Intentionally empty since #346 (ACK moved
    │                              to the management cluster); ready for a
    │                              future application workload
    ├── azure-base/               cert-manager only (the Azure workload
    │                              resources moved to the management cluster,
    │                              #559)
    ├── gcp-base/                 intentionally empty (issue #560: KCC and
    │                              the GCP resources moved to the management
    │                              cluster's workload-resources)
    ├── local-host/               OCI-synced Podinfo workload overlay
    ├── eu-north-01/              Per-cluster overlay (sync target)
    ├── eu-west-01/               Per-cluster overlay (sync target)
    ├── swedencentral-01/         Per-cluster overlay -> azure-base
    └── europe-north1-01/         Per-cluster overlay -> gcp-base