Inputs
What a cluster’s configuration holds beyond socle_version and the cloud object. How to write
it: Configure a cluster.
{ <module> = { <attribute> = <value> } }, typed any, default {}; only
what differs from a default.
| Rule | |
|---|---|
| Names | snake_case; the folder, ResourceSet and namespace use kebab-case (external-dns) |
| Unknown module | refused at plan: kube: unknown module(s) <name>. Catalog: <every module>. |
| Unknown attribute | refused at plan, with the allowed attributes of every module |
| Wrong type | refused at plan: each value must have the kind (string, number, bool, list, object) of its default |
| Module not offered on this cloud | refused at plan: kube: a module is not offered on <cloud>. Cloud-bound modules: … |
Secrets in values | refused at plan, per module: the chart’s secret-bearing paths. Put them in the Secret named by values_secret |
values and a named attribute on the same key | values wins |
Sources: catalog.tf (the schema),
variables.tf (the rules).
The catalog schema
Section titled “The catalog schema”Every module but gateway_api and hello also takes values ({}, any
chart value, yours winning) and values_secret ("", a Secret in the
module’s namespace with a values.yaml key, merged last); not repeated below.
An attribute whose default is a block (monitoring.logs…) is merged one
level deep: a key written alone keeps its siblings at their default.
| Module | Attribute | Default | Meaning |
|---|---|---|---|
hello | enabled | true | podinfo, as a proof that the pipeline works |
hello | replicas | 1 | replicas of the podinfo Deployment |
hello | message | "hello from socle" | the message podinfo serves |
gateway_api | enabled | true | the Gateway API standard CRDs from upstream, pinned by commit, and Cilium’s cilium class on aws and azure. On gcp GKE owns the CRDs and the class, and the module renders the shared Gateways only. Off orphans the CRDs |
gateway_api | gateways | true | the shared Gateways gateway-system/public and private, on aws (once the foundations issued gateway_certificate), azure, and gcp (once they issued the certificate map and the regional certificate, gcp.gateway_certificate) |
crossplane | enabled | false | Crossplane and, per cloud, its providers (on aws IAM, EKS and S3; on gcp the Google Cloud ones, as the federated principal the foundations’ gcp.crossplane grants): the tooling each module declares its cloud access through. Off leaves the CRDs and orphans every module role still declared |
crossplane | permissions_boundary | "" | aws: the boundary every module role carries; an IAM policy ARN. The aws root wires it from the foundations’ crossplane_permissions_boundary_arn. Refused on gcp |
crossplane | dns_records_role | "" | gcp: the project custom role Crossplane grants external-dns to write DNS records, on the project (a zone’s own IAM is never honoured); projects/<id>/roles/<role>. The gcp root wires it from the foundations’ dns_records_role. Refused elsewhere |
external_dns | enabled | false | DNS records for Services, Ingresses and HTTPRoutes in the cloud’s zone. On aws and gcp with crossplane on, its own access (an IAM role on aws, a binding of the DNS records role on gcp); elsewhere the client brings a credential. The aws and gcp roots turn it on when the foundations’ gateway_certificate is set and Crossplane can give it access |
external_dns | domain_filters | [] | zones it may write to, as DNS names; at least one when enabled |
external_dns | policy | "upsert-only" | upsert-only never deletes a record; sync also deletes what it owns |
external_dns | txt_owner_id | the cluster name | the owner in the TXT registry, so two clusters never fight over a zone |
argocd | enabled | true | ArgoCD, the client’s GitOps layer |
argocd | admin_enabled | true | keep the local admin account; false once SSO exists |
argocd | domain | "" | the host ArgoCD is served at (configs.cm.url and the HTTPRoute), a lowercase FQDN; empty means no URL and no route |
argocd | gateway | "private" | the shared Gateway its HTTPRoute attaches to: private, public, or "" for none |
argocd | ha | false | the chart’s HA layout: Redis HA, two replicas of server, repo-server and applicationset |
argocd_image_updater | enabled | false | Argo CD Image Updater, watching argocd; refused while argocd is off |
argocd_image_updater | repositories | [] | aws: the ECR repositories of the cluster’s account and region its own read-only role may read, globs allowed, never * alone; needs ecr in aws.crossplane.allowed_services. gcp: LOCATION/REPOSITORY in the cluster’s project, read through roles/artifactregistry.reader on the whole project, which must be in gcp.crossplane.allowed_roles; one registry entry per location named. Both need crossplane on. []: no role, no registry entry |
argocd_image_updater | registries | [] | the client’s other registries, as Image Updater’s registries.conf writes them, after the socle’s ECR or Artifact Registry entries; credentials a secret:, pullsecret:, env: or ext: reference. config.registries is refused in values |
monitoring | enabled | true | the victoria-metrics-k8s-stack chart as one release: the operator, VictoriaMetrics, vmagent scraping the cluster, kube-state-metrics, node-exporter, and the parts below |
monitoring | metrics | { retention = "15d", storage_size = "20Gi" } | VictoriaMetrics single-node. retention: whole hours, days, weeks or years (15d, 4w, 1y), at least a day; storage_size: the claim on the default StorageClass, in Gi or Ti, "" for an emptyDir |
monitoring | logs | { enabled = true, retention = "7d", storage_size = "20Gi" } | VictoriaLogs single-node, the same shape; off also stops otel_agent |
monitoring | traces | { enabled = false, retention = "7d", storage_size = "10Gi" } | VictoriaTraces single-node, the same shape. Off: pre-GA, an upgrade may drop stored traces |
monitoring | grafana | { enabled = true, domain = "", gateway = "private" } | Grafana with the stack’s datasources and dashboards; domain the host it is served at (server.root_url and the HTTPRoute), empty for none; gateway the shared Gateway its HTTPRoute attaches to: private, public, or "" |
monitoring | alerting | { enabled = false, watchdog = true, receivers = [], route = {}, receivers_secret = "" } | vmalert and Alertmanager with kube-prometheus-stack’s rules. receivers and route as Alertmanager writes them, keys only through *_file fields; receivers_secret a Secret in monitoring, mounted under /etc/alertmanager/secrets/; watchdog an always-firing alert to its watchdog-url key. On needs a receiver and, with watchdog, receivers_secret |
otel_agent | enabled | true | the OpenTelemetry Collector as a DaemonSet: container logs to VictoriaLogs, while monitoring.logs is on |
otel_gateway | enabled | true | the OpenTelemetry Collector as a one-replica Deployment: the applications’ OTLP and the Kubernetes events |
keda | enabled | false | KEDA, event-driven autoscaling down to zero |
keda | services | [] | aws and gcp: the services KEDA’s own identity may read — on aws from sqs, cloudwatch, kinesis, dynamodb, each service also in aws.crossplane.allowed_services; on gcp pubsub, read through Cloud Monitoring, roles/monitoring.viewer also in gcp.crossplane.allowed_roles. Needs crossplane on |
metrics_server | enabled | true | the metrics.k8s.io API behind kubectl top and HPAs on CPU or memory. aws only |
metrics_server | ha | false | two replicas on two nodes, with a disruption budget |
kyverno | enabled | false | the Kyverno engine, no policy |
kyverno_policies | enabled | false | the socle’s policy set, every policy in Audit; needs kyverno |
kyverno_policies | profile | "baseline" | baseline, or restricted for baseline plus six |
kyverno_policies | enforce | [] | policies switched to Enforce, each compiled into a native ValidatingAdmissionPolicy; only names this configuration renders |
kyverno_policies | allowed_registries | [] | registries images may come from; empty means no registry policy |
external_secrets | enabled | false | External Secrets Operator |
external_secrets | prefixes | [<cluster_name>] | aws with crossplane on: the module’s read-only role reads secret:<prefix>/*, and the ClusterSecretStore secret-manager is created on it. Needs secretsmanager in aws.crossplane.allowed_services. On gcp secrets are named <prefix>_<name>, so a prefix is letters, digits and - (no _, no /). []: no role, no store |
reloader | enabled | false | Stakater Reloader, opt-in per workload by annotation; reloader.autoReloadAll is refused in values |
velero | enabled | false | Velero, backups the applications opt into by label. aws and gcp; needs crossplane on and the bucket’s access in the foundations’ allowlist: s3 in aws.crossplane.allowed_services, roles/storage.objectAdmin in gcp.crossplane.allowed_roles |
velero | policies | seven pairs | [{ frequency, retention, schedule }], one Schedule each: hourly 24h and 48h, daily 7d and 30d, weekly 30d and 90d, monthly 90d |
velero | node_agent | eks_addons.efs_csi | the privileged node-agent DaemonSet, which backs EFS volumes up by file system. Refused on gcp: Autopilot forbids its hostPath, and every volume is a PD snapshot |
What each module installs and refuses in values: its catalog page.
Cilium, CoreDNS and the EKS add-ons
Section titled “Cilium, CoreDNS and the EKS add-ons”Three objects, each any, validated like kube; every key optional.
| Variable | Attribute | Default | Where | Meaning |
|---|---|---|---|---|
cilium | enabled | true | aws, azure | install Cilium, and CoreDNS on aws, before Flux. false is for a cluster that brings its own CNI and DNS; the e2e k3s is the only such cluster |
cilium | hubble | false | aws, azure | adds Hubble Relay and UI; Hubble in the agent is always on |
cilium | gateway_api | true | aws, azure | Cilium’s Gateway controller and the cilium class |
cilium | values | {} | aws, azure | any Cilium chart value, merged after the socle’s. Private keys are refused: name a Secret through the chart’s existingSecret fields |
coredns | values | {} | aws, with Cilium | any CoreDNS chart value, merged after the socle’s. Nothing is refused: the chart takes no secret inline |
eks_addons | pod_identity_agent | true | aws | the Pod Identity Agent; the two storage drivers need it |
eks_addons | ebs_csi | true | aws | the EBS CSI driver, with its own role |
eks_addons | efs_csi | false | aws | the EFS CSI driver, with its own role |
eks_addons | snapshot_controller | true | aws | the CSI snapshot controller and its CRDs |
| Refused | When |
|---|---|
cilium | on gcp and scaleway |
coredns | where the socle installs no CoreDNS |
eks_addons | on every cloud but aws |
ebs_csi, efs_csi | with pod_identity_agent = false |
in cilium.values (chart 1.20.2) | tls.ca.key, hubble.tls.server.key, hubble.relay.tls.client.key, hubble.relay.tls.server.key, hubble.ui.tls.client.key, hubble.metrics.tls.server.key, clustermesh.config.clusters[*].tls.key |
- Not attributes: chart versions, IPAM and routing mode, kube-proxy
replacement, operator replicas, resources, the GatewayClass name; all but
the chart versions are reachable through
values. cluster_network(api_endpoint,service_cidron aws,pod_cidron azure) comes from the foundations’ outputs; you never write it. Neither do you write, on gcp,project({ id, number }, the catalog’sinputs.cluster.projectIdandprojectNumber),gateway_certificate_maporgateway_regional_certificate: the gcp root wires them.
The bootstrap module’s variables
Section titled “The bootstrap module’s variables”Generated from opentofu/bootstrap;
the roots’ own variables: OpenTofu modules
and clusters/gcp.
Requirements
Section titled “Requirements”| Name | Version |
|---|---|
| terraform | >= 1.10 |
| aws | >= 6.0, < 7.0 |
| helm | >= 3.0, < 4.0 |
Modules
Section titled “Modules”No modules.
Resources
Section titled “Resources”| Name | Type |
|---|---|
| aws_eks_addon.ebs_csi | resource |
| aws_eks_addon.efs_csi | resource |
| aws_eks_addon.pod_identity_agent | resource |
| aws_eks_addon.snapshot_controller | resource |
| aws_iam_role.ebs_csi | resource |
| aws_iam_role.efs_csi | resource |
| aws_iam_role_policy_attachment.ebs_csi | resource |
| aws_iam_role_policy_attachment.efs_csi | resource |
| helm_release.cilium | resource |
| helm_release.coredns | resource |
| helm_release.instance | resource |
| helm_release.operator | resource |
| helm_release.socle | resource |
| helm_release.storage_class | resource |
| aws_caller_identity.current | data source |
Inputs
Section titled “Inputs”| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| cloud | Which cloud this cluster runs on. Selects the artifact's clusters/ | string | n/a | yes |
| cluster_name | Cluster this socle serves. Stamped on every object the operator creates, and exposed to the catalog as inputs.cluster.name. | string | n/a | yes |
| environment | Environment this cluster serves. Stamped as a label, exposed as inputs.cluster.environment, and the axis the upgrade rings follow. | string | n/a | yes |
| owner | Team accountable for the cluster. Stamped as a label on every object. | string | n/a | yes |
| artifact_pull_secret | Name of an existing kubernetes.io/dockerconfigjson Secret in flux-system that Flux uses to pull the artifact from a private registry. Empty for a public registry. The Secret is created outside this module — a credential never enters OpenTofu. | string | "" | no |
| artifact_url | OCI repository the socle artifact is pulled from. Override for a mirror; the tag is socle_version. | string | "oci://ghcr.io/do-now-io/socle/flux-modules" | no |
| cilium | The socle's Cilium, on the clouds whose foundations create a cluster with no CNI — aws and azure — as { enabled, hubble, gateway_api }, every keyoptional: enabled (true) installs it before Flux; false means thecluster brings its own CNI and DNS, which only a test double does. hubble (false) adds Hubble Relay and UI. gateway_api (true) makesCilium serve the cilium GatewayClass. values ({}) is any Cilium chartvalue, merged over the socle's so the client wins; private keys are refused there, and the chart's existingSecret fields name a Secretinstead. Refused on gcp and scaleway, where the cloud operates Cilium. Typed any and validated like kube, so a misspelt key is an error atplan. Chart versions are pinned in cilium.tf. | any | {} | no |
| cluster_network | What Cilium needs to know about the cluster, from the foundations' outputs, never from the client: api_endpoint, the API server as EKSreturns it (https://host) or AKS does (a bare FQDN), for kube-proxy replacement; service_cidr, the service range, whose .10 is CoreDNS'saddress on aws; pod_cidr, Cilium's pool on azure, where the VNet holdsnodes only. Required wherever the socle installs Cilium, ignored elsewhere. | object({ | null | no |
| coredns | The CoreDNS the socle installs on aws, right after Cilium, as{ values }: values ({}) is any CoreDNS chart value, merged over thesocle's so the client wins — extra zones, forwarders, plugins. The chart has no value that takes secret material inline; a Secret is mounted by name through extraSecrets, or read through env[].valueFrom. Refusedwhere the socle installs no CoreDNS: every cloud but aws, and aws with cilium.enabled = false. Chart version pinned in cilium.tf. | any | {} | no |
| cosign_identity | Keyless identity the artifact's signature must match, as issuer and subject regexes. Defaults to the socle's release workflow on main, so production never consumes a branch build by accident. Override on a dev cluster testing a branch. Null means this default. Verification cannot be disabled. | object({ | { | no |
| eks_addons | The EKS-managed add-ons the socle installs on aws once the nodes run, as{ pod_identity_agent, ebs_csi, efs_csi, snapshot_controller }, everykey optional: pod_identity_agent (true) is what hands every Pod Identity associationits credentials — Crossplane's AWS providers included — and flux-operator waits for it; ebs_csi (true) is the block-storage driver, with its ownrole; efs_csi (false) the RWX one, with its own role. Both drivers needthe agent. snapshot_controller (true) is the CSI snapshot controllerand its CRDs, what the velero module's EBS snapshots need. Refused on every cloud but aws. Versions pinned in eks_addons.tf. | any | {} | no |
| flux_components | Flux controllers to install. The image automation pair is absent by default: the socle's version moves through a reviewed tfvars change, not through a controller rewriting tags. | list(string) | [ | no |
| flux_version | Flux version the operator installs and keeps converged. 2.x tracks the latest 2 series; an exact version pins it. | string | "2.x" | no |
| gateway_certificate_arn | On aws, the ACM certificate the shared Gateways' load balancers terminate TLS with — the foundations' gateway_certificate_arn output, never the client's. Null or empty on aws means no shared Gateway, and no route attached to one. Unknown at plan on the apply that issues it, which is why nothing validates it here. Ignored elsewhere. | string | null | no |
| gateway_certificate_map | On gcp, the Certificate Manager map the public shared Gateway's global load balancer terminates TLS with — the foundations' gateway_certificate_map output, never the client's. Null or empty on gcp means no shared Gateway, and no route attached to one. Unknown at plan on the apply that creates it, which is why nothing validates it here. Ignored elsewhere. | string | null | no |
| gateway_regional_certificate | On gcp, the regional Certificate Manager certificate the private shared Gateway's internal load balancer terminates TLS with — the foundations' gateway_regional_certificate output, never the client's. Null or empty on gcp means no shared Gateway either: a private listener without it would never be programmed. Unknown at plan on the apply that creates it, which is why nothing validates it here. Ignored elsewhere. | string | null | no |
| helm_timeout_seconds | How long to wait for each release to become ready. The instance release is the slow one: its health check waits for the operator to converge the controllers. | number | 600 | no |
| instance_size | Resource profile the operator applies to the controllers. Empty is the operator's own default; small, medium and large scale requests and limits together. | string | "" | no |
| kube | The catalog modules this cluster enables and their values, as{ <module> = { <attribute> = <value> } }. List only what differs fromthe catalog's defaults; an absent module is at its default. Module names are snake_case. Typed any on purpose: a map(any) refuses two modules withdifferent attributes, and an object type silently drops a misspelt attribute — the validations below are what makes a typo an error at plan. The schema is catalog.tf; docs/reference/inputs.md lists it module by module. | any | {} | no |
| network_policy | Let the operator install network policies isolating the Flux namespace. On by default; Cilium enforces them on every cloud we ship. | bool | true | no |
| operator_version | Chart version of flux-operator, which is also the operator's own version. Pinned exactly: the operator is pre-1.0 and its minors are not a stable contract. | string | "0.60.0" | no |
| project | On gcp, the project the cluster runs in, as { id, number } — thefoundations' project_id and project_number outputs, never the client's. Exposed to the catalog as inputs.cluster.projectId and projectNumber: a module's Workload Identity principal names the project number, and the pool named after the project id. Required on gcp, refused elsewhere. | object({ | null | no |
| region | Region the cluster runs in, exposed to the catalog as inputs.cluster.region. Regional cloud APIs need it — on AWS the Pod Identity associations the crossplane module creates. Empty when the caller does not know it; a module that needs it says so. | string | "" | no |
| schedulable_nodes | How many nodes the foundations give the cluster before this module starts — on aws, the bootstrap node group's size. Everything but Cilium waits for it: Cilium's DaemonSet is what makes those nodes Ready, so it is installed beside them, and CoreDNS and the Flux operator, the first releases that need a scheduled pod, come after. Zero fails the plan with that reason, instead of Helm waiting helm_timeout_seconds for a node. Null skips the check, on a cluster whose compute this module cannot see. | number | null | no |
| socle_version | Tag of the socle artifact to pull. Null means this module's own version, so that one bump of the module tag moves module and artifact together. Set it only on a dev cluster testing a branch build, together with cosign_identity. | string | null | no |
| storage_class | Storage class for the source-controller's artifact cache. Empty uses the cluster default. | string | "" | no |
Outputs
Section titled “Outputs”| Name | Description |
|---|---|
| artifact | The socle artifact, as OCI URL and tag. |
| cilium | Whether this module installed Cilium (aws, azure: the clouds whose foundations create a cluster with no CNI), with the chart versions it pinned. installed is false where the cloud operates Cilium, or when cilium.enabled is false. |
| cosign_identity | Keyless identity the artifact's signature is verified against, on every reconciliation. |
| eks_addons | The EKS-managed add-ons this module installed (aws only), each as its pinned version, and for the two storage drivers the ARN of the role their controller runs as. Null for an add-on not installed. |
| flux_version | Flux version the operator converges the controllers to. |
| inputs | What this module ships into the cluster as the ResourceSetInputProvider's defaultValues, after normalisation against the catalog. The catalog's templates read exactly these paths. |
| namespace | Namespace holding the operator, the Flux controllers and the socle's inputs. |
| operator_version | Version of flux-operator installed, which is also its chart version. |
| socle_version | Tag of the socle artifact the cluster pulls. |