Building GitOps CI/CD Pipelines: Terraform, FluxCD, Docker, Kubernetes, Helm & Kustomize - Part 2

Building GitOps CI/CD Pipelines: Terraform, FluxCD, Docker, Kubernetes, Helm & Kustomize - Part 2
Photo by Anastasios Antoniadis / Unsplash

This tutorial continues the series on building GitOps pipelines with Terraform, FluxCD, Docker and Kubernetes.


Tutorial series outline:


In part one of this series, we provisioned our infrastructure, primarily Google Kubernetes Engine (GKE) and Cloud SQL, with rotatable secrets already baked in. (Rotating the SQL user password is as simple as bumping the local.postgres_user_db_password_version in the sql.tf file.)

Before we move forward, let’s configure access to our Google Kubernetes cluster using the gcloud CLI, as we will need this later on. I’ve also recorded a short video showing how to set up your CLI. If you’re already configured, feel free to skip ahead to the next section.

0:00
/1:47

Setup google kubernetes engine for gcloud cli

Now that we've finished infra setup, let's setup and configure FluxCD.

What is FluxCD?

FluxCD is a GitOps tool for Kubernetes. It watches your Git repository and automatically keeps your cluster in sync with the configs you define there. Anytime you update code or infrastructure in Git, FluxCD applies those changes to Kubernetes making deployments consistent, automated, and traceable.

FluxCD is open-source and free to use, and it’s one of the key tools for enabling pull-based continuous delivery in Kubernetes. You might be wondering: why pull-based CD matter? Well here's quick summary and difference between push-based CD and pull-based CD

Aspect Push-based CD (Traditional) Pull-based CD (GitOps: FluxCD)
How it works CI/CD pipeline pushes changes into the cluster In-cluster agent pulls desired state from Git
Initiator External CI/CD system Kubernetes controller inside the cluster
Flow Code → CI builds → CI connects to cluster → kubectl apply Code → CI builds → Pushes Docker Images → FLux montiors Docker images → Updates Git → Flux syncs state
Security CI needs direct access to cluster (creds, RBAC) Cluster fetches from Git, no external access needed
Drift handling Only applied during pipeline run Continuous reconciliation, fixes drift automatically
Simplicity Easier initial setup Slightly more advanced setup, but scales better
Best use case Small/simple projects, lower security concerns Large teams, production, compliance & security needs

👉 In short: Push means you tell the cluster what to do. Pull means the cluster keeps itself in sync with Git, the single source of truth.


Today, we’ll use Terraform to set up and manage FluxCD in our Kubernetes cluster. While you could optionally use the CLI for the FluxCD installation (see here), I prefer Terraform because it makes managing upgrades, patches, and ongoing maintenance to FluxCD much easier when everything is defined as code. It also allows Terraform to both provision and tear down the infrastructure automatically, without requiring a human in the loop.

In the GitHub repo I shared in Part 1 (see link), you’ll find a folder named part-2 containing replacement files and instructions. Here’s what to do:

  • Copy the file named part-1/terraform/init/terraform.tfvars => part-2/terraform/init/terraform.tfvars (you should have created this file in Part 1), and change the line with github_working_directory = part-2/terraform/infrastructure notice the change from part-1 to part-2 🙂.
  • Next, review the file part-2/terraform/infrastructure/variables.tf and ensure all settings are valid and consistent with what you configured in Part 1 of this tutorial series. For brevity, I’ve included the relevant section below.

    The most important variable to double-check is flux_repository, as it must exactly match your setup.
############################################
# Added for Part 2
############################################

variable "github_organization" {
  description = "Github organization name, use this to specify the github organization name to use for terraform cloud this can be either github individual or a team org account"
  type        = string
}

variable "flux_cluster_name" {
  description = "FluxCD cluster name"
  type        = string
  default     = "primary-cluster"
}

variable "flux_repository" {
  description = "FluxCD Github repository name, use this to specify the github repository used for fluxcd to manage the cluster state"
  type        = string
  default     = "tutorial-gitops-fluxcd" # Change this to wwhatever you named your repository in part-1 -> provision the infra -> step 6
}

variable "flux_repository_branch" {
  description = "FluxCD git repository branch to use"
  type        = string
  default     = "main"
}

variable "github_token" {
  description = "GitHub personal access token for API access"
  type        = string
  sensitive   = true
}

part-2/terraform/infrastructure/variables.tf

  • Now switch to the part-2/terraform/init folder and run terraform plan review the proposed changes carefully, and once you’re satisfied, apply them with: terrform apply. This updates your Terraform Cloud deployment to begin watching Part 2 for changes, since we’ll be installing Flux from this section.
  • Finally, switch to the part-2/terraform/infrastructure folder and terraform init This is required since here are new changes in the providers.tf file in this part of series, and they need to be re-initialised. Once re-initialised, run a terraform plan to review summary of changes. Now add a whitespace to any file, git commit and push to trigger the terraform cloud run

All of the changes in this step include installing FluxCD and configuring a Cloud NAT router. The NAT router enables our GKE cluster to access the internet, which is required for pulling container images from GitHub’s registry where the FluxCD controllers are hosted.

For brevity, I’ve included the FluxCD configuration file below, with inline comments to explain what each section is doing.

############################################
# 1) Google Client Config (current gcloud context)
############################################
# This data source fetches details from your currently authenticated
# gcloud user or service account. It provides things like:
#  - The active project ID
#  - The active region/zone
#  - An OAuth2 access token (used by providers like Kubernetes/Flux)
#
# Terraform uses this so it knows *who* you are in Google Cloud
# and can reuse your credentials for API calls.
data "google_client_config" "default" {}

############################################
# 2) Kubernetes Provider (how Terraform talks to your cluster)
############################################
# Terraform needs cluster connection details to install Flux CRDs/resources.
# These values typically come from the GKE data source and your gcloud auth.
provider "kubernetes" {
  # API server endpoint for your GKE cluster
  host = "https://${google_container_cluster.primary.endpoint}"

  # OAuth2 access token from your local gcloud context / service account
  token = data.google_client_config.default.access_token

  # The cluster CA cert is base64-encoded by GKE; decode for the provider
  cluster_ca_certificate = base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate)
}

############################################
# 3) Flux Provider (lets Terraform configure Flux itself)
############################################
# The Flux provider needs BOTH:
#  - Kubernetes connection (same cluster details as above)
#  - Git connection (where your cluster config lives)
provider "flux" {
  kubernetes = {
    # Same cluster endpoint and token used above
    host                   = "https://${google_container_cluster.primary.endpoint}"
    token                  = data.google_client_config.default.access_token
    cluster_ca_certificate = base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate)
  }

  git = {
    # Https URL to your GitHub repo (org + repo provided via variables created from part-1)
    url = "https://github.com/${var.github_organization}/${var.flux_repository}.git"

    # Branch Flux should reconcile from (e.g., "main")
    branch = var.flux_repository_branch

    http = {
      username = "git"
      password = var.github_token
    }
  }
}

############################################
# 4) Bootstrap Flux against the Git repo
############################################
# This installs Flux into the cluster and points it at your Git repo/path.
# Flux will reconcile whatever manifests live under the specified path.
resource "flux_bootstrap_git" "this" {
  # Path in the repo where cluster config lives, e.g., clusters/prod
  path = "clusters/${var.flux_cluster_name}"

  # Include extra controllers for image automation (optional but common)
  components_extra   = ["image-reflector-controller", "image-automation-controller"]
  embedded_manifests = true

  # Make sure the Google Kubernetes Engine cluster exists before bootstrapping
  depends_on = [google_container_cluster.primary, google_compute_router_nat.main]
}

part-2/terraform/infrastructure/flux.tf

You can now monitor progress in the Terraform Cloud dashboard. If everything has run successfully, both the plan and apply should complete without errors. Next, verify the following:

Terraform Cloud Dashboard

  • The plan and apply have both completed successfully without errors.

Flux GitHub Repository

  • The repository is now populated with Flux manifest files.
  • This confirms that Flux has write access to the repo.

Google Kubernetes Cluster

  • A new namespace (usually flux-system) has been created.
  • All Flux controller pods (source-controller, kustomize-controller, helm-controller, etc.) are running.

📌 If any of these checks fail, tear everything down and redeploy to ensure a clean setup.

See the images below for reference, your setup should look the same.

Successful Terraform cloud run
Github FluxCD Manifests
GKE FluxCD workloads

Alternatively you can use the following command in the cli to confirm the pods are running:

kubectl -n flux-system get pods

NAME                                          READY   STATUS    RESTARTS   AGE
helm-controller-5c898f4887-sh8n9              1/1     Running   0          37m
image-automation-controller-cdfcd6bd7-7ldzl   1/1     Running   0          37m
image-reflector-controller-6cc7b7669c-sf4qv   1/1     Running   0          37m
kustomize-controller-7bcf986f97-jw8g4         1/1     Running   0          37m
notification-controller-5f66f99d4d-ssdn6      1/1     Running   0          37m
source-controller-54bc45dc6-sccxd             1/1     Running   0          37m

🎉 Congratulations! If you’ve made it this far, you’ve successfully set up FluxCD on your GKE cluster using Terraform.

When you have some time, go back and review the inline comments in the files. They’re there to guide you and provide extra context on what’s happening under the hood. If you’d like to dive deeper, you can also explore the official FluxCD documentation.

👉 In the next tutorial, we’ll set up a GitHub Actions pipeline to prepare for deploying applications using FluxCD.

Thanks for reading!