CtrlK
BlogDocsLog inGet started
Tessl Logo

nitinjain999/platform-skills

Production-grade platform engineering handbook — Kubernetes, Terraform, Flux CD, GitHub Actions, AWS, and more.

67

Quality

84%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

fluxcd-terraform.mdreferences/

FluxCD Terraform Bootstrap Reference

Bootstrap Flux Operator and a FluxInstance into a Kubernetes cluster via Terraform. Designed for clusters provisioned with Terraform where Flux takes over GitOps reconciliation after bootstrap.


Ownership model

LayerOwns
TerraformEphemeral bootstrap: namespace, RBAC, mounted manifests, bootstrap Job
Flux OperatorSteady-state: all Flux resources once the Job completes

Terraform creates an ephemeral Kubernetes Job that applies the FluxInstance manifest. After the Job succeeds, Flux owns itself and Terraform stops touching cluster state.


Repository layout

repo/
├── terraform/              # Terraform root module
│   ├── main.tf
│   └── variables.tf
└── clusters/               # Flux fleet source of truth
    └── staging/
        └── flux-system/
            └── flux-instance.yaml

Since Terraform lives in a subdirectory, reference the repo root with ${path.root}/.. when loading manifests.


Resource categories

GitOps resources — create-once, owned by Flux

Applied once with create-if-missing semantics. After Flux bootstraps, it owns these:

InputPurpose
instance_yaml (required)The FluxInstance manifest
operator_chartFlux Operator Helm chart repo, version, values
prerequisites.yamlsOrdered manifests applied before FluxInstance
prerequisites.chartsHelm charts installed before Flux (e.g. cert-manager)

Managed resources — reconciled every bootstrap run

Server-side applied on every Job run:

InputPurpose
secrets_yamlMulti-document Secret manifest
runtime_infoKey-value data published as flux-runtime-info ConfigMap

Managed resources are tracked in an inventory and garbage-collected when removed from input.


Runtime info and variable substitution

When managed_resources.runtime_info is set, the Job:

  1. Creates a flux-runtime-info ConfigMap with the provided key-value pairs
  2. Substitutes ${variable} references in all input manifests using flux envsubst --strict
managed_resources = {
  runtime_info = {
    CLUSTER_REGION = "eu-west-1"
    ACCOUNT_ID     = "123456789012"
    ENVIRONMENT    = "staging"
  }
}

Terraform and Git co-ownership of the same ConfigMap

Terraform and Git can each own separate fields in flux-runtime-info via SSA field ownership:

AuthorityFields
TerraformCLUSTER_REGION, ACCOUNT_ID (infra facts)
Git / FluxARTIFACT_TAG, ENVIRONMENT, CLUSTER_NAME (app facts)

The Git-managed ConfigMap must include:

metadata:
  annotations:
    kustomize.toolkit.fluxcd.io/ssa: "Merge"

This ensures kustomize-controller merges fields rather than replacing the whole object.


Sync authentication

Match the Secret name to spec.sync.pullSecret (defaults to flux-system):

managed_resources = {
  secrets_yaml = <<-EOT
    apiVersion: v1
    kind: Secret
    metadata:
      name: registry-auth
      namespace: flux-system
    type: kubernetes.io/dockerconfigjson
    stringData:
      .dockerconfigjson: '{"auths":{"ghcr.io":{"auth":"${base64encode("user:${var.ghcr_token}")}"}}}'
  EOT
}

Auth options:

MethodContents
Git PATusername, password in stringData
GitHub AppgithubAppID, githubAppInstallationOwner, githubAppPrivateKey — preferred over PATs
OCI registrykubernetes.io/dockerconfigjson type

For OCI secrets embedded in YAML heredocs: single quotes inside JSON must be escaped — replace(var.secret, "'", "''").

Secret values never appear in Terraform state — only a SHA-256 hash is persisted.


Node scheduling

Configure at three layers when using dedicated or tainted nodes:

# 1. Bootstrap Job tolerations
job = {
  tolerations = [{
    key      = "dedicated"
    operator = "Equal"
    value    = "flux"
    effect   = "NoSchedule"
  }]
  host_network = false    # set true if CNI must be installed before pod networking
}

# 2. Flux Operator via operator_chart.values_yaml
operator_chart = {
  values_yaml = <<-EOT
    tolerations:
      - key: dedicated
        operator: Equal
        value: flux
        effect: NoSchedule
  EOT
}
# 3. Flux controllers via FluxInstance spec.kustomize.patches
spec:
  kustomize:
    patches:
      - target:
          kind: Deployment
          name: kustomize-controller
        patch: |
          - op: add
            path: /spec/template/spec/tolerations
            value:
              - key: dedicated
                operator: Equal
                value: flux
                effect: NoSchedule

Drift and revision

  • The bootstrap Job reruns automatically when any input changes
  • No changes → terraform plan shows zero diff
  • Use revision (integer counter) to force a Job rerun without changing content
  • Terraform-managed resources on the cluster are not tracked in state — the Job manages them

Shared operator values file

A single flux-operator-values.yaml can serve both Terraform (bootstrap) and Flux (steady-state upgrades):

operator_chart = {
  values_yaml = file("${path.root}/../flux-operator-values.yaml")
}

For bootstrap-specific overrides, use shallow merge() — note it replaces entire top-level keys, not nested fields.


Debugging

debug_on_failure = true   # relays Job logs on failure

Requires bash, kubectl, and the hashicorp/null provider in the execution environment.

# Check bootstrap Job status
kubectl get jobs -n flux-system
kubectl logs -n flux-system job/flux-bootstrap

# Check FluxInstance after bootstrap
kubectl get fluxinstance flux -n flux-system
kubectl get fluxreport flux -n flux-system -o yaml

Full example

module "flux_bootstrap" {
  source = "github.com/controlplaneio-fluxcd/terraform-flux-operator//modules/cluster"

  kubeconfig_path = "~/.kube/config"
  kubeconfig_context = "staging"

  gitops_resources = {
    instance_yaml = file("${path.root}/../clusters/staging/flux-system/flux-instance.yaml")
  }

  managed_resources = {
    runtime_info = {
      CLUSTER_NAME   = "staging"
      CLUSTER_REGION = var.region
      ENVIRONMENT    = "staging"
    }
    secrets_yaml = templatefile("${path.root}/templates/registry-secret.yaml", {
      token = var.ghcr_token
    })
  }
}

BEFORE_AFTER.md

CHANGELOG.md

CODE_OF_CONDUCT.md

COMMANDS.md

CONTRIBUTING.md

EDITOR_INTEGRATIONS.md

GETTING_STARTED.md

HOW_IT_WORKS.md

install.sh

INSTALLATION.md

LAUNCH.md

PROMPTS.md

QUICKSTART.md

README.md

renovate.json

SECURITY.md

SKILL.md

tessl.json

tile.json