Architecting Declarative Infrastructure: Integrating Terraform and Flux for GitOps

The convergence of infrastructure as code and GitOps has fundamentally altered how engineering teams manage Kubernetes environments. Terraform and Flux represent the two pillars of this modern operational paradigm. Terraform excels at provisioning cloud resources, such as Kubernetes clusters, networking components, and managed services. Flux, conversely, is a popular GitOps toolkit for Kubernetes designed to keep clusters in sync with configuration stored in Git repositories. When combined, these tools create a powerful end-to-end pipeline where Terraform provisions and configures the underlying cloud infrastructure, while Flux handles the continuous delivery of applications. This integration allows teams to separate concerns clearly: infrastructure teams manage the substrate, while application teams manage the workloads. This article details the technical implementation of this combination, ranging from cluster creation and Flux bootstrapping to advanced patterns for reconciling state without introducing ownership conflicts.

Understanding the Synergy Between Terraform and Flux

To understand the value of integrating these tools, one must first recognize their distinct domains of expertise. Terraform is a declarative configuration tool that connects directly to cloud providers to create and manage resources. It is the natural place to install Flux right after a cluster comes up, primarily because credentials are already in scope and providers are wired into the execution environment. However, Flux operates on a different plane. It excels at keeping Kubernetes clusters synchronized with Git repositories containing application manifests. Together, they cover the entire lifecycle from infrastructure creation to application delivery, with each tool handling what it does best.

The typical workflow involves three distinct phases. First, Terraform creates the Kubernetes cluster and installs Flux. Second, Flux takes over and deploys applications from Git. Third, both tools continue to manage their respective domains independently. This separation allows infrastructure and application teams to work independently while maintaining a consistent deployment process. The combination provides declarative management for the entire stack, extending from cloud resources to running applications. To implement this, specific prerequisites are required. Engineers need a cloud provider account, Terraform version 1.5 or later, the Flux CLI installed, a GitHub personal access token with repository permissions, and kubectl configured for cluster access.

Provisioning the Kubernetes Cluster with Terraform

The first step in the pipeline is establishing the Kubernetes control plane. This is achieved using Terraform to create the underlying infrastructure. A robust setup requires defining the necessary providers and modules. The configuration must specify the required version of Terraform and the specific versions of the providers for AWS, Kubernetes, Flux, GitHub, and TLS.

The following configuration snippet illustrates the provider requirements for a setup that targets Amazon Web Services. The configuration enforces Terraform version 1.5 or later and pins specific major versions for the providers to ensure reproducibility and stability.

```hcl

providers.tf

Define required providers for the Terraform configuration

terraform {
requiredversion = ">= 1.5"
required
providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
flux = {
source = "fluxcd/flux"
version = "~> 1.0"
}
github = {
source = "integrations/github"
version = "~> 6.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}

provider "aws" {
region = var.aws_region
}
```

Once the providers are defined, the actual cluster creation begins. Using the terraform-aws-modules/eks module is a standard approach for creating Amazon Elastic Kubernetes Service (EKS) clusters. The module configuration specifies the cluster name, version, and node group details. In this example, the cluster is named flux-demo-cluster and runs Kubernetes version 1.34. The node group is configured with a desired size of 3, a minimum of 2, and a maximum of 5 nodes, using t3.medium instance types.

```hcl

cluster.tf

Create the EKS cluster that Flux will manage

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
clustername = "flux-demo-cluster"
cluster
version = "1.34"
clusterendpointpublicaccess = true
vpc
id = module.vpc.vpcid
subnet
ids = module.vpc.privatesubnets
eks
managednodegroups = {
default = {
desiredsize = 3
min
size = 2
maxsize = 5
instance
types = ["t3.medium"]
}
}
}

Configure providers that depend on the cluster

provider "kubernetes" {
host = module.eks.clusterendpoint
cluster
cacertificate = base64decode(module.eks.clustercertificateauthoritydata)
exec {
apiversion = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster
name]
}
}
```

The kubernetes provider configuration is critical. It utilizes the exec block to dynamically generate an authentication token using the AWS CLI. This ensures that the Terraform process has the correct credentials to interact with the newly created cluster without manually managing static credentials.

Bootstrapping Flux with the Terraform Provider

Once the cluster is online, the next step is to install Flux. The official Flux provider for Terraform is a plugin that enables the bootstrapping of Kubernetes clusters using Flux v2. This provider simplifies the process by handling the creation of the necessary namespaces, roles, and deployments required for Flux to function.

The Flux provider configuration requires access to the Kubernetes cluster and the Git repository where the Flux manifests will be stored. In this example, the provider is configured to connect to the EKS cluster using the same exec authentication method as the kubernetes provider. Additionally, it is configured to use SSH to access a GitHub repository.

```hcl

flux.tf

Configure the Flux provider

provider "flux" {
kubernetes = {
host = module.eks.clusterendpoint
cluster
cacertificate = base64decode(module.eks.clustercertificateauthoritydata)
exec = {
apiversion = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster
name]
}
}
git = {
url = "ssh://[email protected]/${var.githuborg}/${var.githubrepository}.git"
ssh = {
username = "git"
privatekey = tlsprivatekey.flux.privatekey_pem
}
}
}

Generate an SSH key pair for Flux to access the Git repository

resource "tlsprivatekey" "flux" {
algorithm = "ECDSA"
ecdsa_curve = "P256"
}
```

The tls_private_key resource generates an ECDSA key pair with the P256 curve. This key is used by Flux to authenticate with the Git repository over SSH. The public key must be added to the GitHub repository's SSH keys to allow Flux to pull the configuration. The provider for Terraform supports various bootstrapping scenarios, including using GitHub with Personal Access Tokens (PAT), SSH, and GPG signing, as well as support for GitLab and Forgejo repositories.

Resolving Ownership and Reconciliation Conflicts

A significant challenge in integrating Terraform and Flux is the potential for ownership conflicts. Once Flux is online, every object that Terraform applied is now an object that Flux wants to reconcile. Traditional workarounds, such as using the fluxcd/flux provider or chained helm_release resources, can keep Terraform on the hook for steady-state reconciliation forever. This creates a dependency where Terraform must continue to manage resources that should ideally be owned by the GitOps pipeline.

To address this, a more advanced approach uses a module that implements a create-if-missing strategy. In this pattern, Terraform owns only the bootstrap mechanism, which includes a namespace, temporary RBAC, and a Kubernetes Job that applies the Flux Operator and the FluxInstance. Once the Flux Operator is running, it adopts the resources, and Terraform stops touching them. This clean handoff ensures that Flux retains control over the application state, while Terraform manages only the initial infrastructure setup. This approach fixes the ownership handoff problem by ensuring that Terraform is not responsible for steady-state reconciliation of objects that Flux manages.

Terraform Controller and GitOps Automation

For scenarios where Terraform resources need to be managed directly by Flux, the Terraform Controller provides a powerful solution. The Terraform Controller requires at least Flux version 0.32, which in turn needs at least Kubernetes version 1.20.6. This controller allows Terraform resources to be defined as Kubernetes manifests and reconciled by Flux.

The installation of the Terraform Controller can be done by adding a HelmRelease to the bootstrap repository. Once installed, the controller handles the heavy lifting of executing Terraform plans and applies. To define the source of Terraform resources, a Source controller is used. This can be a GitRepository, Bucket, or OCIRepository. An example of a GitRepository source is shown below.

yaml apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: GitRepository metadata: name: helloworld namespace: flux-system spec: interval: 30s url: https://github.com/tf-controller/helloworld ref: branch: main

The GitOps automation mode can be enabled by setting spec.approvePlan=auto. In this mode, Terraform resources will be planned and automatically applied. This enables a fully automated pipeline where changes to Terraform configurations in Git trigger the reconciliation of cloud resources.

Best Practices for Production Environments

Implementing this pipeline in a production environment requires adherence to several best practices to ensure reliability and security. First, store Flux manifests in the same repository that Flux watches so that changes are automatically applied. This tight coupling ensures that any change to the configuration is immediately reflected in the cluster. Second, use Flux's variable substitution to inject Terraform outputs into application manifests. This allows application configurations to dynamically reference infrastructure details, such as service endpoints or database connection strings, without hardcoding them.

Third, implement Flux's image automation to keep container images up to date. This feature monitors container images for new releases and automatically updates the deployment manifests, ensuring that applications run the latest stable versions. Fourth, monitor Flux's reconciliation status to catch drift quickly. Drift occurs when the actual state of the cluster diverges from the desired state defined in Git. Regular monitoring helps identify and resolve these issues before they impact production. Finally, pin Flux component versions in your Terraform configuration for reproducibility. This ensures that the cluster is always provisioned with a known, tested version of Flux, preventing unexpected behavior due to upstream changes.

Comparison of Bootstrap Strategies

The choice of bootstrap strategy can impact the complexity and reliability of the setup. The table below compares the traditional provider-based approach with the create-if-missing module approach.

Feature Traditional Provider Approach Create-If-Missing Module Approach
Ownership Terraform manages steady-state reconciliation Terraform owns only the bootstrap mechanism
Reconciliation Terraform remains on the hook forever Flux adopts resources; Terraform stops touching them
Complexity Simpler initial setup Requires custom module for handoff
Risk Potential conflicts with Flux Clean separation of duties
Recommendation Suitable for simple, small-scale setups Recommended for production environments

Security and Credential Management

Security is a paramount concern when integrating Terraform and Flux. The Flux provider for Terraform takes security and user trust very seriously. If a security issue is believed to be found in the Terraform Flux Provider, it should be reported through the established security policy. The documentation for the provider is available on the Terraform provider website.

When using SSH for Git access, it is crucial to manage the SSH keys securely. The tls_private_key resource generates a key pair that is used for authentication. The private key should be stored in a secure location, such as a Kubernetes secret, to prevent unauthorized access. Additionally, the GitHub personal access token used for authentication should be granted the minimum necessary permissions, such as repo permissions, to avoid over-privileging.

The Terraform Controller also handles security considerations. It executes Terraform plans within the cluster, so it is essential to ensure that the controller has the appropriate RBAC permissions to interact with cloud providers. The approvePlan=auto mode should be used with caution, as it automatically applies changes. In a production environment, it may be beneficial to use a manual approval process or to restrict automatic application to non-critical resources.

Conclusion

The integration of Terraform and Flux creates a robust GitOps pipeline that leverages the strengths of both tools. Terraform handles cluster provisioning and the initial Flux bootstrap, while Flux continuously reconciles application state from Git. This separation allows infrastructure and application teams to work independently while maintaining a consistent deployment process. The combination provides declarative management for the entire stack, from cloud resources to running applications. By addressing ownership conflicts through careful design and implementing best practices for security and monitoring, organizations can achieve a highly reliable and scalable deployment pipeline. The use of the Terraform Controller further extends this capability, allowing Terraform resources themselves to be managed via GitOps, thereby closing the loop between infrastructure and application management.

Sources

  1. OneUptime Blog
  2. GitHub - FluxCD Terraform Provider
  3. FluxCD Blog - Terraform Flux Operator Bootstrap
  4. FluxCD Blog - How to GitOps Your Terraform

Related Posts