Automate your Infrastructure on AWS using Terraform Controller and FluxCD
Introduction In the age of cloud-native operations, infrastructure automation is no longer optional—it's essential. While tools like Terraform have long been the standard for Infrastructure as Code (IaC), integrating them into a GitOps workflow brings unprecedented control, traceability, and scalability. In this blog, we'll explore how to automate your AWS infrastructure using Terraform Controller (from the Crossplane ecosystem) and FluxCD, leveraging Git as the single source of truth. Why Terraform Controller + FluxCD? Terraform Controller: A Kubernetes controller that allows you to manage Terraform executions through Custom Resource Definitions (CRDs). FluxCD: A GitOps toolkit that automates Kubernetes deployments by syncing Git repositories with clusters. GitOps Benefits: Version control, audit trails, CI/CD integration, and reduced configuration drift. Prerequisites Before you begin, ensure you have the following: A Kubernetes cluster (EKS,AKS,GKS,On-Prem or local like KinD) AWS credentials with necessary IAM permissions kubectl, flux, and terraform controller installed A Git repository for storing Terraform manifests Step-by-Step Guide Install Kind Cluster #cat config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker #kind create cluster --config=config.yaml Install flux cli #brew install fluxcd/tap/flux BootStrap fluxcd to install flux-system with gitrepo infra-demo #flux bootstrap gitlab --token-auth --owner=infra-demo2 --repository=flux-system --branch=main --path=clusters/dev A namespace name flux-system will be created with required components #kubectl get ns #kubectl get all -n flux-system Lets Install Terraform Controller #helm repo add tofu-controller https://flux-iac.github.io/tofu-controller #helm upgrade -i tofu-controller tofu-controller/tf-controller \ --namespace flux-system Lets Verify Controller in flux-system #kubectl get pods -n flux-system | grep tf Create infrastructure project inside infra-demo repo and map Source list and Kustamization to infra-demo repository to managed by Flux-cd #cat infra-demo.yaml apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: GitRepository metadata: name: vpc-creation namespace: flux-system spec: interval: 30s url: https://gitlab.com/infra-demo2/infrastructure.git ref: branch: main secretRef: name: flux-system --- apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 kind: Kustomization metadata: name: vpc-creation namespace: flux-system spec: prune: true interval: 2m path: ./ sourceRef: kind: GitRepository name: vpc-creation timeout: 3m healthChecks: - apiVersion: infra.contrib.fluxcd.io/v1alpha1 kind: Terraform name: vpc-creation namespace: flux-system Lets Create terraform Code in Infrastructure Project to deploy VPC in AWS Account #cat main.tf resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_support = true enable_dns_hostnames = true tags = { Name = "${var.project}-vpc" } } resource "aws_subnet" "public" { count = length(var.public_subnets) vpc_id = aws_vpc.main.id cidr_block = var.public_subnets[count.index] availability_zone = element(var.availability_zones, count.index) map_public_ip_on_launch = true tags = { Name = "${var.project}-public-subnet-${count.index + 1}" } } resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project}-igw" } } resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project}-public-rt" } } resource "aws_route" "internet_access" { route_table_id = aws_route_table.public.id destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } resource "aws_route_table_association" "public_assoc" { count = length(var.public_subnets) subnet_id = aws_subnet.public[count.index].id route_table_id = aws_route_table.public.id } #cat outputs.tf output "vpc_id" { value = aws_vpc.main.id } output "public_subnet_ids" { value = aws_subnet.public[*].id } #cat variables.tf variable "aws_region" { default = "us-east-1" } variable "project" { default = "demo" } variable "vpc_cidr" { default = "10.0.0.0/16" } variable "public_subnets" { default = ["10.0.1.0/24", "10.0.2.0/24"] } variable "availability_zones" { default = ["us-east-1a", "us-east-1b"] } Note: Not Recommended #cat provider.tf provider "aws" { region = var.aws_region access_key = "XXXXXXXXXX" secret_key = "YYYYYYYYYYYYYY" } Now Create Terraform Controller to Deploy Terraform Code #cat terraform.yaml apiVersion: infra.contrib.fluxcd.io/v1alpha1 kind: Terraform metadata: name: vpc-creation namespace: flux-system spec: interv

Introduction
In the age of cloud-native operations, infrastructure automation is no longer optional—it's essential. While tools like Terraform have long been the standard for Infrastructure as Code (IaC), integrating them into a GitOps workflow brings unprecedented control, traceability, and scalability. In this blog, we'll explore how to automate your AWS infrastructure using Terraform Controller (from the Crossplane ecosystem) and FluxCD, leveraging Git as the single source of truth.
Why Terraform Controller + FluxCD?
- Terraform Controller: A Kubernetes controller that allows you to manage Terraform executions through Custom Resource Definitions (CRDs).
- FluxCD: A GitOps toolkit that automates Kubernetes deployments by syncing Git repositories with clusters.
- GitOps Benefits: Version control, audit trails, CI/CD integration, and reduced configuration drift.
Prerequisites
Before you begin, ensure you have the following:
- A Kubernetes cluster (EKS,AKS,GKS,On-Prem or local like KinD)
- AWS credentials with necessary IAM permissions
- kubectl, flux, and terraform controller installed
- A Git repository for storing Terraform manifests
Step-by-Step Guide
Install Kind Cluster
#cat config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
#kind create cluster --config=config.yaml
Install flux cli
#brew install fluxcd/tap/flux
BootStrap fluxcd to install flux-system with gitrepo infra-demo
#flux bootstrap gitlab --token-auth --owner=infra-demo2 --repository=flux-system --branch=main --path=clusters/dev
A namespace name flux-system will be created with required components
#kubectl get ns
#kubectl get all -n flux-system
Lets Install Terraform Controller
#helm repo add tofu-controller https://flux-iac.github.io/tofu-controller
#helm upgrade -i tofu-controller tofu-controller/tf-controller \
--namespace flux-system
Lets Verify Controller in flux-system
#kubectl get pods -n flux-system | grep tf
Create infrastructure project inside infra-demo repo and map Source list and Kustamization to infra-demo repository to managed by Flux-cd
#cat infra-demo.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
name: vpc-creation
namespace: flux-system
spec:
interval: 30s
url: https://gitlab.com/infra-demo2/infrastructure.git
ref:
branch: main
secretRef:
name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: vpc-creation
namespace: flux-system
spec:
prune: true
interval: 2m
path: ./
sourceRef:
kind: GitRepository
name: vpc-creation
timeout: 3m
healthChecks:
- apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
name: vpc-creation
namespace: flux-system
Lets Create terraform Code in Infrastructure Project to deploy VPC in AWS Account
#cat main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnets[count.index]
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-public-subnet-${count.index + 1}"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project}-igw"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project}-public-rt"
}
}
resource "aws_route" "internet_access" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_route_table_association" "public_assoc" {
count = length(var.public_subnets)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
#cat outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
#cat variables.tf
variable "aws_region" {
default = "us-east-1"
}
variable "project" {
default = "demo"
}
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
variable "public_subnets" {
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "availability_zones" {
default = ["us-east-1a", "us-east-1b"]
}
Note: Not Recommended
#cat provider.tf
provider "aws" {
region = var.aws_region
access_key = "XXXXXXXXXX"
secret_key = "YYYYYYYYYYYYYY"
}
Now Create Terraform Controller to Deploy Terraform Code
#cat terraform.yaml
apiVersion: infra.contrib.fluxcd.io/v1alpha1
kind: Terraform
metadata:
name: vpc-creation
namespace: flux-system
spec:
interval: 1m
approvePlan: auto
path: ./
sourceRef:
kind: GitRepository
name: vpc-creation
namespace: flux-system
Now Lets Deploy infrademo.yaml to Map Repo to get managed by flux-system
#flux get sources all | grep vpc-creation
#flux get kustomization | grep vpc-creation
# kubectl apply -f infrademo.yaml
#flux get sources all | grep vpc-creation
#flux get sources all | grep vpc-creation
Once we Deploy it Terraform Controller is going to Create a runner with name of vpc-creation-tf-runner which is going to deploy the resources in AWS
Lets verify the log of the runner
kubectl logs vpc-creation-tf-runner -n flus-system -f
Congratulations: We have deployed our resource successfully
Lets Verify from the AWS Portal