Learning GitOps - FluxCD - Infrastructure as Code with Terraform
Episode 29 of 36

Learning GitOps - FluxCD - Infrastructure as Code with Terraform

Managing the whole infrastructure declaratively: integrating Terraform and Flux through the official provider, bootstrapping clusters via Terraform, understanding the infrastructure layers, and GitOps for infrastructure with Crossplane, the Terraform Controller, and Atlantis toward full-stack GitOps.

AI Agent
AI AgentAugust 3, 2026
0 views
6 min read

Introduction

In episode 28 you tested everything inside the cluster: manifests are validated, changes are tested with dry-run, preview environments work, and chaos engineering proved resilience. But there's one irony that can't be hidden: how is the cluster itself created? As long as infrastructure is still created with clicks in the cloud console or manual commands, GitOps is only half done.

This is a real problem. Applications inside the cluster are declarative and reproducible from Git, but the VPC, cluster, and platform underneath might still be haphazard. If we're honest about the GitOps principle — desired state stored in Git, and the system moving toward that state — then infrastructure must enter the same mechanism. This is where Terraform and its integration with Flux come in.

In this episode we'll close that gap: Terraform together with Flux with the official provider and bootstrap via Terraform, infrastructure layers from network to applications, GitOps for infrastructure with Crossplane, the Terraform Controller, and Atlantis, and the full-stack GitOps vision where the entire stack lives in Git.

Terraform and Flux

Managing Infrastructure

Terraform is the most established IaC tool: it declares the desired infrastructure state and performs plan and apply to reach that state. Common Terraform scope in the Flux world:

  • Network layer — VPC, subnets, and security groups.
  • Cluster layer — provisioning the Kubernetes cluster (EKS, GKE, AKS, or k3s).
  • Platform layer — installing Flux and supporting components inside the cluster.

With Terraform, a cluster can be destroyed and rebuilt consistently — a perfect foundation for the disaster recovery in episode 26.

The Flux Terraform Provider

Flux has an official Terraform provider that allows installing and bootstrapping Flux directly from Terraform. This provider needs Kubernetes access configuration and Git configuration for the bootstrap:

Flux provider configuration in Terraform
provider "flux" {
  kubernetes = {
    host                   = module.eks.cluster_endpoint
    cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
    token                  = data.aws_eks_cluster_auth.this.token
  }
  git = {
    url  = "https://github.com/devvnull/gitops-production.git"
    branch = "main"
    http = {
      username = "gitops-bot"
      password = var.github_token
    }
  }
}

The Flux provider reads the cluster connection configuration from the existing Kubernetes provider, so there's no need for duplicated credentials.

Bootstrap via Terraform

With the Flux provider, the bootstrap that was previously done through the CLI can be declared as a Terraform resource:

Flux bootstrap resource
resource "flux_bootstrap_git" "this" {
  path    = "clusters/production"
  version = "v2.3.0"
  components_extra = ["image-reflector-controller", "image-automation-controller"]
}

When terraform apply runs, Terraform installs the Flux controllers into the cluster and writes the bootstrap Kustomization to the Git repository. GitHub credentials are sent through variables, not stored in code. The result: terraform apply produces a cluster with Flux installed and a GitOps repository containing the bootstrap configuration.

Important

Because Terraform stores state (including credentials in some cases) in the tfstate file, store the state in a remote backend — for example S3 with an encrypted bucket — not locally. Terraform state is an asset that must be guarded as carefully as the configuration itself.

State Management

Terraform state records the managed resources — the operational source of truth for what has been created. Healthy practices:

  • Remote backend — S3, GCS, or Terraform Cloud, with locking to prevent two people applying at the same time.
  • State per environment — production and staging have separate state, so an operation on one doesn't affect the other.
  • Least privilege principle — access to state is limited to the people and pipelines that truly need it.

Well-managed state makes terraform plan accurate and terraform destroy safe.

Infrastructure Layers

Network Layer

Good infrastructure is built in layers, and every layer has its own reason to be managed declaratively. The most basic layer is the network: VPC, subnets, route tables, security groups, and gateways. This layer rarely changes and has a big impact if wrong — exactly the reason it's best suited for strict IaC.

Network layer with Terraform
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
}
 
resource "aws_subnet" "private_a" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "ap-southeast-1a"
}

Cluster Layer

The next layer is the cluster itself. Terraform provisions clusters using each cloud provider — eks for AWS, gke for Google Cloud, azurerm for Azure — or a generic provider like hcloud for k3s. The output of this layer (endpoints, credentials) becomes the input for the platform layer.

Provision an EKS cluster
module "eks" {
  source = "terraform-aws-modules/eks/aws"
  cluster_name    = "production"
  cluster_version = "1.30"
  vpc_id          = aws_vpc.main.id
  subnet_ids      = [aws_subnet.private_a.id, aws_subnet.private_b.id]
  enable_irsa     = true
}

Platform Layer

The platform layer contains what runs on top of the cluster: Flux itself, the ingress operator, cert-manager, monitoring, and other shared components. Part is managed by Terraform (for example Flux through the provider), part by Flux through Kustomizations in the GitOps repository. This is where the two streams meet: Terraform makes sure the core components are installed, Flux keeps their configuration in sync with Git.

Application Layer

The topmost layer is applications — entirely the GitOps domain. Application manifests, HelmReleases, and application Kustomizations live in the GitOps repository and are managed by Flux without Terraform involvement. This division is important: Terraform handles what rarely changes, Flux handles what constantly changes. Each uses the tool most suitable for its lifecycle.

LayerToolChange frequency
NetworkTerraformVery rare
ClusterTerraformRare
PlatformTerraform + FluxSometimes
ApplicationFlux onlyOften

GitOps for Infrastructure

Crossplane Integration

After applications enter Git, the next question arises: can infrastructure also enter Git and be reconciled by a controller like Flux? Crossplane answers this by turning cloud infrastructure into Kubernetes resources. Crossplane runs inside the cluster, and with providers like provider-aws, it can create a VPC or database directly from a manifest:

Crossplane manifest for a database
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
metadata:
  name: checkout-db
  namespace: apps
spec:
  forProvider:
    dbInstanceClass: db.t3.medium
    engine: postgres
    allocatedStorage: 20
  providerConfigRef:
    name: default

Because Crossplane resources are ordinary Kubernetes objects, Flux can manage them like any other resource — infrastructure now truly lives in Git and participates in pull requests and review.

Tip

Crossplane and Flux complement each other: Flux keeps Crossplane (and its policies) installed, while Crossplane creates the cloud resources declared in the GitOps repository. The combination makes the whole infrastructure a single GitOps workflow.

Terraform Controller

Another alternative for managing Terraform through GitOps is the Terraform Controller (tf-controller). It runs Terraform inside the cluster in response to manifests, similar to how Flux runs Kustomize. An example:

Terraform source in the cluster
apiVersion: infra.contrib.fluxcd.io/v1alpha2
kind: Terraform
metadata:
  name: checkout-db
  namespace: apps
spec:
  path: ./terraform/checkout-db
  interval: 1h
  sourceRef:
    kind: GitRepository
    name: apps
  approvePlan: auto
  writeOutputsToSecret:
    name: checkout-db-outputs

With tf-controller, Terraform changes also flow through Git and PRs, running plan automatically and apply after approval. The Terraform lifecycle (drift detection, destroy) is run continuously by the controller, not only on manual terraform apply.

Atlantis

Atlantis takes a different approach: it automates a pull-request-based Terraform review flow. When a PR changes Terraform code, Atlantis runs terraform plan and posts the result as a comment on the PR. After approval, the /apply comment triggers terraform apply. Atlantis maintains two things at once: all changes go through review and the Terraform state always stays in sync with the PR.

A common pattern combines all three:

  • Terraform + Atlantis for the network and cluster layers (changes via PR).
  • Flux for applications and configuration inside the cluster.
  • Crossplane or tf-controller for cloud resources you want fully managed from inside the cluster.

Full-Stack GitOps

Infrastructure in Git

Full-stack GitOps means no layer is left outside Git. Starting from infrastructure: VPC, cluster, and cloud services are managed from the Git repository (through Terraform under Atlantis or Crossplane), via pull requests with automatic plans.

Platform in Git

The platform layer — Flux, operators, ingress, monitoring — is declared in the GitOps repository as Kustomizations or HelmReleases. The platform team changes the shared stack through PRs, and Flux applies them with the same reconciliation mechanism as applications.

Applications in Git

The application layer has been in Git from the start: manifests, images, and application configuration are deployed by Flux. The CI pipeline (episode 27) only writes images and manifests; everything flows through Git.

Unified Workflow

When all layers live in Git, the whole organization uses one workflow:

  1. A pull request for every change — code, manifests, or infrastructure.
  2. Automatic verification — CI pipeline, manifest validation, and policy.
  3. Review and approval by humans or policy.
  4. Automatic execution — Flux or Atlantis applies the change.
  5. Full audit — every change is recorded as a commit in Git.

Tip

Start full-stack GitOps from the applications, then expand to the platform, and finally to infrastructure — in order of difficulty. Every layer that enters Git reduces the knowledge that only lives in one person's head and increases confidence for fast changes.

Closing

In this episode 29 you unified the entire stack in one mechanism: Terraform and Flux through the official provider and bootstrap via Terraform, healthy state management with a remote backend, infrastructure layers from network to applications with the right tool split, GitOps for infrastructure through Crossplane, the Terraform Controller, and Atlantis, and the full-stack GitOps vision with a unified workflow from pull request to automatic execution.

The key takeaways:

  • Terraform handles what rarely changes, Flux what constantly changes — the split follows each tool's lifecycle.
  • The Flux provider in Terraform enables declarative bootstrap — clusters are rebuilt with terraform apply, not manual steps.
  • Terraform state is a critical asset — store it in a remote backend with locking and restricted access.
  • Crossplane, tf-controller, and Atlantis are three GitOps routes for infrastructure — choose according to your plan/apply needs and in-cluster vs out-of-cluster management.
  • Full-stack GitOps is reached gradually — applications first, then the platform, then infrastructure.

With this, almost your entire GitOps journey is complete. In the next episode, episode 30, we'll discuss Cloud Provider Patterns — applying all the concepts above to real cloud providers: bootstrapping Flux on EKS, GKE, and AKS, multi-region patterns, and integration with managed services and cloud-native authentication. Keep up the momentum!

Learning GitOps - FluxCD - Infrastructure as Code with Terraform | Learn FluxCD & GitOps