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

Configure a cluster

A cluster is one main.tf, in a directory of its own, that calls the socle’s root for its cloud.

Before you start: your cloud’s prerequisites are met (AWS, GCP). AWS and GCP have a root; on Azure and Scaleway, the foundations take a tfvars file of their own (Azure, Scaleway).

clusters/
dev/main.tf
prod/main.tf

Each has its own state key, so a plan on dev never touches prod. Commit the .terraform.lock.hcl tofu init writes next to it.

clusters/prod/main.tf
terraform {
backend "s3" {
bucket = "acme-tofu-state"
key = "socle/aws/acme-prod.tfstate"
region = "eu-west-3"
use_lockfile = true
}
}
locals {
socle_version = "0.1.0" # x-release-please-version
}
module "socle" {
source = "oci://ghcr.io/do-now-io/socle/opentofu-modules//opentofu/clusters/aws?tag=${local.socle_version}"
socle_version = local.socle_version
aws = {
region = "eu-west-3"
cluster_name = "acme-prod"
owner = "platform"
environment = "prod"
kubernetes_version = "1.34"
availability_zones = ["eu-west-3a", "eu-west-3b", "eu-west-3c"]
cluster_endpoint_public_access_cidrs = ["203.0.113.0/24"]
gateway_certificate = { domain = "acme.example" }
}
kube = {
argocd = { domain = "argocd.acme.example" }
kyverno = { enabled = true }
}
}
clusters/prod/main.tf
terraform {
backend "gcs" {
bucket = "acme-prod-tofu-state"
prefix = "socle/gcp/acme-prod"
}
}
locals {
socle_version = "0.1.0" # x-release-please-version
}
module "socle" {
source = "oci://ghcr.io/do-now-io/socle/opentofu-modules//opentofu/clusters/gcp?tag=${local.socle_version}"
socle_version = local.socle_version
gcp = {
project_id = "acme-prod"
region = "europe-west1"
cluster_name = "acme-prod"
owner = "platform"
environment = "prod"
create_network = true
maintenance_window = {
start_time = "2026-01-03T02:00:00Z"
end_time = "2026-01-03T14:00:00Z"
recurrence = "FREQ=WEEKLY;BYDAY=SA"
}
gateway_certificate = { dns_zone = "acme-example", domains = ["acme.example", "*.acme.example"] }
}
kube = {
argocd = { domain = "argocd.acme.example" }
kyverno = { enabled = true }
}
}

One socle cluster per project: every module’s Workload Identity principal names the project, never the cluster (GCP prerequisites).

BlockWhat it holds
socle_versionA released version, never latest or main; the only line an upgrade touches, source included.
awsThe AWS foundations’ variables. Keys without a default, kubernetes_version among them, are required. Every option: prod.tfvars.example, written as tfvars; the same keys go in the module block.
gcpThe GCP foundations’ variables. Keys without a default, maintenance_window among them, are required; there is no Kubernetes version, the release channel decides. Every option: prod.tfvars.example.
kubeOnly what differs from the catalog’s defaults, in snake_case. Defaults: Inputs; modules: catalog.

Every module with a chart takes values, any value of its chart. Yours win over the socle’s defaults and over the module’s named attributes:

kube = {
argocd = {
domain = "argocd.acme.example"
values = {
configs = {
cm = { "accounts.alice" = "apiKey, login" }
rbac = { "policy.csv" = "g, alice, role:admin" }
}
}
values_secret = "argocd-values"
}
}

values lands in the OpenTofu state, so secrets are refused there at plan. Put them in a Secret with a values.yaml key, in the module’s namespace, and name it in values_secret:

Terminal window
kubectl -n argocd create secret generic argocd-values --from-file=values.yaml
kubectl -n argocd label secret argocd-values reconcile.fluxcd.io/watch=Enabled

The Secret is merged last and optional; the label applies a change at once. OpenTofu never reads it.

4. Adjust what precedes Flux, if you need to

Section titled “4. Adjust what precedes Flux, if you need to”

On AWS the root also takes cilium, coredns and eks_addons, every key optional (Inputs), beside aws and kube in the module block:

cilium = { hubble = true }
coredns = { values = { replicaCount = 3 } }
eks_addons = { efs_csi = true }

Leave cilium.enabled at true on a real EKS: the cluster has no other network.

On GCP there is nothing to adjust: GKE Autopilot runs the network (Dataplane V2), the cluster DNS and the nodes, and the root takes none of these blocks.

Terminal window
cd clusters/prod
tofu init
tofu plan
tofu apply

A misspelt name, a wrong type, a module not offered on your cloud or a secret in values fails the plan (Troubleshooting). A green apply is not convergence: check kubectl -n flux-system get resourceset socle-root (Enable a module).