Curtis' Blog

Automate EKS with Terraform and FluxCD: Passing Outputs from Infra to GitOps

Curtis Goolsby

When managing Kubernetes infrastructure at scale, it’s common to use Terraform to provision the cluster and FluxCD (or ArgoCD) to deploy workloads onto it. However, a frequent challenge arises: how do you pass dynamic values—like EKS endpoints or IAM ARNs—from Terraform into your GitOps configuration?

In this post, we’ll walk through a practical and production-ready method to bridge that gap using a ConfigMap and FluxCD’s postBuild.substituteFrom capability.


Why This Pattern Matters

Certain values in an AWS EKS cluster—like:

  • module.eks.cluster_endpoint
  • aws_efs_file_system.efs_dns_name
  • data.aws_caller_identity.current.account_id
  • aws_iam_role.ebs_csi_role.arn

…are not known until after Terraform runs. But if you’re using FluxCD or ArgoCD to manage Kubernetes components, you need these values available at GitOps runtime.

Our solution: output the values from Terraform into a ConfigMap inside the flux-system namespace, then reference that ConfigMap in FluxCD using a substituteFrom block.


Step 1: Bootstrap the Flux Namespace

Terraform should not directly manage the lifecycle of the flux-system namespace, especially if Flux is self-managing. To avoid terraform destroy side effects, use a null_resource to create the namespace imperatively:

resource "null_resource" "create_flux_ns" {
  provisioner "local-exec" {
  command = <<-EOT
    export KUBECONFIG="$(mktemp)"
    aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_name} --kubeconfig "$KUBECONFIG"
    kubectl get ns flux-system || kubectl create ns flux-system
  EOT
    interpreter = ["/bin/bash", "-c"]
  }

  triggers = {
    cluster_endpoint = module.eks.cluster_endpoint
  }

  depends_on = [module.eks]
}

Step 2: Export Terraform Outputs into a ConfigMap

We now create a ConfigMap to expose useful Terraform output values to FluxCD.

resource "kubernetes_config_map" "terraform_outputs" {
  metadata {
    name      = "terraform-outputs"
    namespace = "flux-system"
  }

  data = {
    AWS_ACCOUNT_ID     = data.aws_caller_identity.current.account_id
    EBS_CSI_ROLE_ARN   = aws_iam_role.ebs_csi_role.arn
    CLUSTER_NAME       = var.cluster_name
    CLUSTER_ENDPOINT   = module.eks.cluster_endpoint
  }

  lifecycle {
    ignore_changes = [
      metadata[0].annotations,
      metadata[0].labels,
      metadata[0].generation,
      metadata[0].resource_version
    ]
    prevent_destroy = false
  }

  depends_on = [
    module.eks,
    data.kubernetes_namespace.flux_system
  ]
}

This approach allows your Terraform to dynamically feed values into the GitOps pipeline via Kubernetes-native resources.


Step 3: Consume the ConfigMap with FluxCD

FluxCD supports substituteFrom in its Kustomization manifests. This lets you reference the ConfigMap created by Terraform:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: aws-ebs-csi
  namespace: flux-system
spec:
  interval: 10m
  path: ./infra/base/aws-ebs-csi
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  dependsOn:
    - name: flux-system
  postBuild:
    substituteFrom:
      - kind: ConfigMap
        name: terraform-outputs

Inside your Kustomize manifests (infra/base/aws-ebs-csi/), use variable substitution like ${EBS_CSI_ROLE_ARN} to inject these dynamic values.


Summary

This integration pattern lets you:

  • Keep Terraform focused on infrastructure provisioning.
  • Use FluxCD/ArgoCD to manage in-cluster components declaratively.
  • Avoid hardcoding account- or cluster-specific values in your GitOps repos.
  • Scale across environments using dynamic and reproducible workflows.