Mengenal Terragrunt, thin wrapper buatan Gruntwork yang menghapus duplikasi backend dan provider di arsitektur multi-environment, serta fitur include, dependency, inputs, dan run-all untuk menjaga kode Terraform tetap DRY.

Setelah di episode 11 sebelumnya kita membahas bagaimana menjaga kualitas kode melalui terraform fmt, tflint, Checkov, hingga automated testing dengan Terratest — pada episode kali ini kita akan membahas satu lapisan arsitektur yang membuat semua praktik itu terasa lebih ringan: Terragrunt.
Di episode 10, kalian sudah diperkenalkan pada pola directory-based untuk memisahkan environment (dev/, staging/, prod/). Pola itu memang benar secara arsitektur, tapi ada satu masalah yang menyertainya: duplikasi. Setiap folder environment, bahkan setiap komponen di dalamnya, harus mengulang blok backend "s3" { ... }, blok provider "aws" { ... }, dan konfigurasi yang sama berulang-ulang. Bayangkan tim kalian memiliki 5 environment dan 6 komponen infrastruktur di masing-masing environment — itu 30 salinan konfigurasi backend yang identik. Ketika ada perubahan sekecil satu region, kalian harus mengubah 30 file. Di sinilah Terragrunt hadir sebagai jawaban.
Mengapa topik ini penting di dunia kerja nyata? Karena begitu tim tumbuh melewati satu environment, masalah terbesar bukan lagi menulis kode Terraform, melainkan mengelola pengulangan tanpa kehilangan fleksibilitas. Terragrunt adalah tool yang dipakai ribuan perusahaan — termasuk banyak unicorn startup — untuk membuat infrastruktur scale secara konsisten. Kalian akan sangat sering menjumpainya di pekerjaan DevOps/SRE, jadi memahami arsitekturnya sekarang akan membayar mahal di kemudian hari.
Sebelum masuk ke Terragrunt, mari kita perjelas dulu masalah yang ingin dipecahkan. Ingat konfigurasi backend di episode 5:
terraform {
backend "s3" {
bucket = "myapp-tfstate-bucket"
key = "dev/vpc/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "myapp-tfstate-lock"
}
}Sekarang salin blok yang sama ke dev/eks/backend.tf, dev/rds/backend.tf, prod/vpc/backend.tf, dan seterusnya. Belum lagi blok provider yang biasanya ditulis seragam di semua folder:
provider "aws" {
region = "ap-southeast-1"
default_tags {
tags = {
Environment = "dev"
ManagedBy = "terraform"
}
}
}Masalahnya bukan sekadar "kode tidak cantik". Ini masalah maintenance: setiap perubahan konfigurasi backend atau provider harus disebar manual ke seluruh folder, dan sangat mudah ada satu folder yang terlewat. Folder yang terlewat tersebut kemudian memakai konfigurasi lama yang mungkin sudah salah atau tidak aman. Inilah yang disebut masalah DRY violation — kita mengulang hal yang sama di banyak tempat, padahal aturan emas engineering adalah Don't Repeat Yourself.
Note
Modul (episode 9) sudah menyelesaikan masalah duplikasi resource, tapi tidak menyelesaikan masalah duplikasi konfigurasi di sekitar resource — yaitu backend, provider, dan variabel yang sama diulang di setiap folder environment. Terragrunt mengisi celah ini, bukan menggantikan modul.
Terragrunt adalah thin wrapper — lapisan tipis di atas Terraform CLI — yang dibuat oleh Gruntwork (perusahaan yang didirikan Yevgeniy Brikman, penulis buku Terraform: Up & Running). Terragrunt tidak menulis ulang Terraform; ia membaca file konfigurasi sendiri bernama terragrunt.hcl, lalu memanggil terraform di balik layar sesuai instruksi.
Bayangkan Terragrunt seperti manajer proyek yang mengatur para pekerja. Setiap pekerja (Terraform) sangat pintar membangun satu bangunan (satu komponen infrastruktur), tapi mereka butuh koordinasi: blueprint dari mana, material yang sama di mana, urutan kerja antar gedung bagaimana. Terragrunt yang mengkoordinasikan itu semua, sehingga para pekerja tidak perlu dijelaskan ulang hal yang sama setiap kali.
Filosofi Terragrunt dirangkum dalam tiga pilar:
run-all.Untuk memahami Terragrunt, kalian harus menguasai lima keyword berikut. Ini adalah bahasa sehari-hari para pengguna Terragrunt:
| Keyword | Fungsi | Contoh Penggunaan |
|---|---|---|
terraform.source | Lokasi module Terraform yang akan dieksekusi | source = "../modules//vpc" |
include | Mewarisi konfigurasi dari file induk (root terragrunt.hcl) | include "root" { path = find_in_parent_folders() } |
remote_state | Konfigurasi backend state secara terpusat | remote_state { backend = "s3" ... } |
generate | Membuat file .tf otomatis (misal provider.tf) | generate "provider" { path = "provider.tf" ... } |
dependency | Membaca output dari module lain + membuat dependency graph | dependency.vpc.outputs.vpc_id |
inputs | Meneruskan variabel ke module Terraform | inputs = { environment = "prod" } |
run-all | Menjalankan command ke seluruh unit dalam graph | terragrunt run-all plan |
Important
Perhatikan dua gaya penulisan dependency: dependency (blok) digunakan pada unit-level untuk membaca output modul lain, sedangkan terraform_remote_state (data source) adalah cara lama dari Terraform itu sendiri. Terragrunt memilih dependency karena lebih eksplisit, ikut membangun graph, dan memiliki mock_outputs agar plan tetap berjalan meskipun modul dependensinya belum pernah di-apply.
Pola umum repositori Terragrunt dipecah menjadi dua repositori terpisah: modules (kode modul yang bisa di-reuse) dan live (konfigurasi per environment yang memanggil modul). Berikut contoh struktur yang direkomendasikan:
infrastructure-live/
├── terragrunt.hcl # Root: backend + provider + variabel global
├── modules/ # (biasanya repo terpisah: infra-modules)
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── dev/
│ ├── vpc/terragrunt.hcl
│ ├── eks/terragrunt.hcl
│ └── rds/terragrunt.hcl
└── prod/
├── vpc/terragrunt.hcl
├── eks/terragrunt.hcl
└── rds/terragrunt.hclPerhatikan satu hal: tidak ada lagi file .tf di folder environment. Setiap folder hanya berisi satu file terragrunt.hcl. File backend.tf dan provider.tf akan dibangkitkan otomatis oleh Terragrunt saat eksekusi. Inilah kunci DRY: kalian tidak menulisnya manual, Terragrunt yang menuliskannya untuk kalian.
terragrunt.hcl: Backend & Provider TerpusatInti dari seluruh arsitektur ini ada di file root. File ini akan diwarisi (via include) oleh semua folder di bawahnya. Mari kita bedah isinya:
locals {
environment = basename(get_terragrunt_dir())
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<-EOF
provider "aws" {
region = "${local.region}"
default_tags {
tags = {
Environment = "${local.environment}"
ManagedBy = "terragrunt"
}
}
}
EOF
}
remote_state {
backend = "s3"
config = {
bucket = "myapp-tfstate-bucket"
key = "${local.environment}/${path_relative_to_include()}/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "myapp-tfstate-lock"
}
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
}
inputs = {
aws_region = "ap-southeast-1"
environment = local.environment
}Penjelasan bagian-bagian pentingnya:
locals.environment — dihitung otomatis dari nama folder (dev, prod). Ketika file ini diwariskan ke prod/vpc/, nilainya menjadi prod; di dev/eks/ menjadi dev. Satu sumber nilai, banyak pemakaian.generate "provider" — menyuruh Terragrunt membuat file provider.tf di folder unit setiap kali dijalankan. if_exists = "overwrite_terragrunt" memastikan file buatan Terragrunt ditimpa tanpa bertanya. Perhatikan kita tidak perlu menulis file provider.tf manual di folder mana pun.remote_state — satu-satunya tempat definisi backend. Kunci state (key) disusun otomatis dari environment + path relatif, sehingga setiap folder unit punya state masing-masing yang terisolasi, persis filosofi directory-based di episode 10.inputs — nilai yang diteruskan sebagai variabel ke module Terraform, menggantikan file terraform.tfvars yang selama ini diduplikasi.Tip
path_relative_to_include() adalah fungsi Terragrunt yang menghitung path relatif folder unit terhadap folder root. Kombinasi basename(get_terragrunt_dir()) dan path_relative_to_include() ini adalah dua fungsi yang paling sering dipakai untuk membangun key state yang konsisten dan unik.
terragrunt.hcl: include + inputsSetiap folder unit di bawah dev/ atau prod/ hanya perlu tiga hal: warisi root, tunjuk module, dan kirim variabel. Contoh untuk VPC di environment prod:
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "${get_parent_terragrunt_dir()}//modules/vpc"
}
inputs = {
environment = "prod"
vpc_name = "vpc-main"
cidr_block = "10.0.0.0/16"
enable_nat = true
}include "root" — find_in_parent_folders() mencari file terragrunt.hcl terdekat ke atas dan mewarisi seluruh konfigurasinya (provider, backend, inputs). Semua duplikasi dihapus dalam satu langkah.terraform.source — menunjuk lokasi module. Simbol // di ...//modules/vpc menunjukkan bahwa modules/ adalah root module directory dan vpc adalah submodulnya — sintaks standar Terraform registry yang tetap valid di Terragrunt.inputs — nilai spesifik untuk environment ini. Kalian bebas menambah nilai yang sama atau meng-merge dengan nilai global dari root. Inilah fleksibilitas yang selama ini dicari: global di root, spesifik di unit.Note
Ketika sebuah unit mendefinisikan inputs yang sama dengan root, nilai di unit menang (bersifat overriding). Jika ingin mengubah strategi merge agar root selalu menang, kalian bisa mengatur merge_strategy = "deep" atau "no_merge" pada blok include.
dependencyInfrastruktur jarang berdiri sendiri: cluster EKS butuh VPC, database RDS butuh berada di subnet yang dibuat VPC. Terragrunt menghubungkan modul-modul ini lewat blok dependency. Contoh prod/eks/terragrunt.hcl yang memakan output dari prod/vpc:
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "${get_parent_terragrunt_dir()}//modules/eks"
}
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "vpc-00000000"
private_subnet_ids = ["subnet-00000000"]
public_subnet_ids = ["subnet-11111111"]
}
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
private_subnet_ids = dependency.vpc.outputs.private_subnet_ids
public_subnet_ids = dependency.vpc.outputs.public_subnet_ids
}Yang terjadi di balik layar:
config_path membuat Terragrunt tahu bahwa eks bergantung pada vpc.terragrunt plan dijalankan pada eks, Terragrunt otomatis membaca state vpc (melakukan inisialisasi cepat di folder tersebut) lalu memuat nilai outputs-nya.inputs ke module EKS — persis seperti melempar data antar komponen tanpa hardcode.mock_outputs adalah jaring pengaman: nilainya dipakai sementara ketika state vpc belum ada (misal pada run pertama di repo baru), sehingga plan tidak error hanya karena urutan. Di production, nilai asli dari state yang menggantikannya.
Warning
dependency bersifat read-only: Terragrunt membaca output modul lain, tapi tidak pernah otomatis meng-apply modul itu lebih dulu. Jika vpc belum di-apply, output yang dibaca hanya mock_outputs. Biasakan menjalankan terragrunt run-all apply dari root agar seluruh graph di-apply dalam urutan dependensi yang benar, bukan terragrunt apply per folder secara acak.
Terragrunt bisa dijalankan per unit maupun untuk seluruh graph sekaligus. Berikut perintah yang paling sering dipakai:
terragrunt planrun-all adalah salah satu fitur paling berharga. Ia membaca seluruh graph dependensi dari dependency blocks, lalu mengeksekusi unit-unit tersebut secara paralel — namun memastikan unit yang jadi dependensi selesai duluan. Bayangkan dengan 18 unit infrastruktur: satu perintah run-all plan cukup untuk melihat rencana perubahan di seluruh environment, tanpa login ke setiap folder satu per satu.
Perhatikan output mengelompokkan unit berdasarkan urutan eksekusi graph (GROUP). Unit yang tidak saling bergantung berjalan paralel di group yang sama, dan group yang lebih tinggi menunggu group di bawahnya selesai.
Berikut perbandingan singkat untuk memperjelas posisi Terragrunt dalam arsitektur kalian:
| Aspek | Terraform Murni (Directory-based) | Terragrunt |
|---|---|---|
| Konfigurasi backend | Diduplikasi di tiap folder | Ditulis sekali di root, diwariskan |
| Konfigurasi provider | Diduplikasi di tiap folder | Dibangkitkan via generate |
| Membaca output modul lain | terraform_remote_state manual | dependency + mock_outputs |
| Eksekusi multi-folder | Script/shell manual | run-all otomatis searah graph |
| Nilai variabel per env | File .tfvars terpisah | inputs terpusat + override |
| Kompleksitas awal | Rendah | Sedang (perlu memahami keyword) |
| Cocok untuk | Tim kecil, 1–2 environment | Tim besar, banyak env & komponen |
Berikut jebakan yang paling sering ditemui di lapangan:
| Kesalahan | Gejala | Solusi |
|---|---|---|
Melupakan if_exists pada generate | Terragrunt berhenti & meminta konfirmasi di CI | Gunakan overwrite_terragrunt |
Folder tanpa include | Backend/provider tidak terbentuk → init error | Pastikan setiap unit mewarisi root |
key state tidak unik antar unit | Dua unit berbagi state yang sama → saling timpa | Kombinasikan basename(get_terragrunt_dir()) + path_relative_to_include() |
| Meng-apply per folder secara acak | Urutan salah → EKS gagal karena VPC belum ada | Gunakan run-all apply |
Menghapus mock_outputs | Plan error di repo baru/CI clean state | Biarkan mock_outputs selalu ada |
Commit file hasil generate ke Git | Konflik dan kebingungan overwrite | Tambahkan pola provider.tf/backend.tf ke .gitignore |
| Menaruh Terragrunt dan modul dalam satu repo | Ukuran repo membengkak, coupling erat | Pisahkan repo infra-live dan infra-modules |
Caution
Jangan pernah meng-commit file hasil generate (misal provider.tf, backend.tf yang dibuat Terragrunt di folder unit) ke Git. File tersebut adalah artefak yang akan ditimpa Terragrunt; meng-commitnya hanya membuat repo kotor dan konflik saat CI berjalan. Tambahkan ke .gitignore dan biarkan Terragrunt membuatnya saat runtime.
Pada episode 12 ini kita telah membahas bagaimana Terragrunt menyelesaikan masalah duplikasi konfigurasi yang tidak bisa diselesaikan modul biasa. Kita belajar bahwa masalah utama arsitektur multi-environment bukan menulis kode, melainkan mengelola pengulangan backend dan provider. Dengan include, satu file root menjadi sumber kebenaran; dengan generate, file .tf disusun otomatis; dengan dependency, modul saling bertukar output; dan dengan run-all, seluruh environment dieksekusi searah dependency graph hanya dengan satu perintah.
Poin kunci yang perlu kalian bawa pulang:
include (warisan), inputs (variabel), dan dependency (hubungan antar modul).run-all mengubah operasi manual 18 folder menjadi satu perintah.Di episode 13 selanjutnya, kita akan membawa seluruh arsitektur ini ke tingkat berikutnya dengan topik CI/CD Pipeline Automation (GitHub Actions / GitLab CI) — membangun pipeline yang otomatis menjalankan terraform fmt, tflint, security scan, dan plan, dengan approval gate sebelum apply, plus login cloud tanpa password lewat OIDC. Pastikan tetap semangat!