workspace (component)
A Kustomize component that drops one TofuWorkspace custom resource, named workspace, into an app's namespace. Crossplane turns that resource into an OpenTofu workspace that owns the app's external resources — the restic buckets on Hetzner and Backblaze, the Backblaze application key scoped to them, and the *.int.kueber.eu DNS records for its internal routes — so they are declared next to the app and unwound with its namespace.
It is the second component most apps pull in, right after app-network-policy: backups reads the credentials it produces, and http-internal routes get their DNS record from it.
What it composes
TofuWorkspaceworkspace(platform.kueber.eu/v1alpha1) frombase/, with the talos overlay patching in the state location — bucketcrossplane-talos-workspaces-…on Hetzner object storage, keystate.tfstate— and the internal DNS target (dns.address192.168.105.151, the internal gateway, plus the NetBird zone id).- Namespace labels
secrets.tofu-workspace: "true"andsecrets.netbird: "true", patched onto the app'sNamespace, so External Secrets delivers the object-storage and NetBird credentials the workspace needs into the namespace. Secrettofu-encryption— supplied by the app's wrapper (talos/workspace/secret-workspace.yaml, SOPS-encrypted): the passphrase that encrypts the OpenTofu state at rest.
What the composition renders from that (k8s/platform/configs/talos/crossplane/composition-tofu-workspace.yaml):
- a per-namespace
ProviderConfigandWorkspaceforprovider-opentofu, state in the S3 backend above; - for every k8up
Schedulein the namespace labelledcrossplane-workspace: "true", a bucket named after the Schedule'sspec.backend.s3.bucket:minio_s3_bucketon the Hetzner endpoint,b2_bucketplus a bucket-scopedb2_application_keyon Backblaze; - for every internal
HTTPRoute, anetbird_dns_recordA record pointing atdns.address; - the outputs written to the
workspace-outputsconnection Secret — the Backblaze key id and secret thatschedule-backblazein backups reads; Usageobjects that keep theProviderConfigand the credential Secrets alive until the workspace'stofu destroyhas run.
How an app uses it
# k8s/apps/<app>/talos/workspace/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
components:
- ../../../../components/workspace/talos
resources:
- secret-workspace.yaml # tofu-encryption, SOPS
and in the overlay's kustomization.yaml, components: [network-policies, workspace, backups, …]. The wrapper must be included from the overlay that also holds namespace.yaml, because the component patches the Namespace by kind.
Why one workspace per app
- External resources follow the app. A bucket or DNS record that only exists because an app exists should be declared with the app, not in a central tofu environment that has to be edited on every add and remove.
- Credentials stay scoped. Each app gets its own Backblaze application key, restricted to its own bucket; the cluster-wide key that mints them can create buckets and keys but not delete them (backup location: Backblaze).
- Deletion is guarded. Backup buckets render with
prevent_destroy = spec.retainBuckets(defaulttrue), so a careless prune cannot take the backups with it.
Operational notes
- Deleting the namespace deletes everything in it — except that the
TofuWorkspaceunwinds external resources throughtofu destroy, and its backup buckets areprevent_destroy-guarded. Decommissioning an app therefore starts with emptying its bucket and settingspec.retainBuckets: falseon theTofuWorkspace; only then does the namespace terminate cleanly. The composition'sUsages keep the ProviderConfig and credential Secrets alive until the destroy has run. Details in IaC layers. - The bucket suffix (
-<app>-<8 hex>in the app'sbackups/transformer-bucket-name.yaml) is what makes bucket names unique across apps and clusters; it is generated once per app and never changed, or the workspace would create a fresh, empty bucket. - Realtime compositions are off in Crossplane, so a new Schedule or HTTPRoute is picked up on the next reconcile interval, not instantly (Crossplane).
- The component exists for the talos cluster only; edge apps have no k8up and no internal routes, so they carry no workspace.
Composition source
There is no README.md in k8s/components/workspace/talos/; the manifests themselves are short — see k8s/components/workspace/talos and the XRD/Composition under k8s/platform/configs/talos/crossplane.