Dalam episode ini kita akan memahami bagaimana Terraform membangun dependency graph untuk menentukan urutan pembuatan resource, kapan kita perlu memaksa urutan dengan depends_on, serta melindungi infrastruktur dengan lifecycle rules seperti create_before_destroy dan prevent_destroy.

Setelah di episode 6 sebelumnya kita membahas Advanced Expressions, Functions & Loops — mulai dari built-in functions, operator ternary, hingga meta-arguments perulangan count dan for_each — pada episode kali ini kita akan membahas pertanyaan yang langsung muncul begitu infrastruktur kita mulai rumit: bagaimana Terraform tahu harus membuat resource yang mana lebih dulu?
Ketika stack kalian hanya berisi satu bucket S3, urutan tidaklah penting. Namun di dunia nyata, sebuah stack berisi puluhan hingga ratusan resource yang saling terkait: VPC harus ada sebelum subnet, subnet harus ada sebelum instance, security group harus dirujuk oleh load balancer, dan seterusnya. Kalau urutannya salah, semuanya error.
Kabar baiknya, Terraform menyelesaikan masalah ini secara otomatis lewat dependency graph. Namun ada kalanya Terraform tidak bisa menyimpulkan urutan dengan benar, dan ada kalanya kita ingin melindungi resource vital agar tidak hancur oleh kecelakaan. Dua tema inilah yang akan kita bedah tuntas: manajemen dependensi dan lifecycle rules.
Terraform membangun Directed Acyclic Graph (DAG) dari seluruh resource yang dideklarasikan di kode kita. Setiap resource adalah node, dan setiap referensi antar resource adalah edge (panah) yang menunjukkan ketergantungan. Node yang tidak punya dependensi bisa dieksekusi paralel, sedangkan node yang bergantung harus menunggu pendahulunya selesai.
Analoginya seperti membangun rumah: tembok tidak bisa berdiri sebelum fondasi jadi, dan atap baru bisa dipasang setelah tembok berdiri. Seorang kontraktor yang baik membangun semuanya secara paralel jika aman, dan menunggu di titik-titik yang wajib berurutan. Terraform melakukan hal yang sama terhadap cloud — dan graph ini dibuat ulang setiap kali terraform plan berjalan.
Kabar baik pertama: sebagian besar dependensi tidak perlu kita tulis. Ketika sebuah resource mereferensikan atribut resource lain, Terraform otomatis memasang edge pada dependency graph. Ini disebut implicit dependency. Perhatikan contoh stack VPC berikut:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = { Name = "main-vpc" }
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
resource "aws_security_group" "web" {
vpc_id = aws_vpc.main.id
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.app.id
vpc_security_group_ids = [aws_security_group.web.id]
}Tanpa satu baris depends_on pun, Terraform tahu bahwa aws_subnet.app bergantung pada aws_vpc.main (karena vpc_id = aws_vpc.main.id), dan aws_instance.web bergantung pada subnet serta security group. Urutan ini bahkan berlaku sebaliknya saat destroy: instance dihancurkan lebih dulu, baru subnet, baru VPC — prinsip reverse order yang konsisten.
Note
Referensi yang membentuk implicit dependency tidak terbatas pada id. Referensi seperti aws_instance.web.arn, aws_security_group.web.vpc_id, atau referensi dari resource ke data source juga membangun edge. Aturannya sederhana: selama ada nama resource lain muncul di dalam blok atribut sebuah resource, Terraform mencatat ketergantungannya.
depends_onTerkadang dependensi tidak bisa dideteksi secara otomatis. Kasus klasiknya adalah efek samping (side effect) yang tidak terekspos lewat atribut — misalnya sebuah resource baru bekerja dengan benar hanya jika resource lain sudah selesai di-provision, tanpa ada referensi atribut di antara keduanya.
Contoh nyata yang sering membuat developer terjun: me-attach IAM policy ke role sebelum Lambda function mulai dipanggil. Secara teknis, aws_lambda_function tidak mereferensikan atribut dari policy attachment, padahal eksekusi Lambda membutuhkan permission yang sudah terpasang.
resource "aws_iam_role" "lambda_role" {
name = "lambda-exec-role"
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}
resource "aws_iam_role_policy_attachment" "lambda_exec" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "app" {
function_name = "app-handler"
role = aws_iam_role.lambda_role.arn
# Terraform tidak bisa melihat relasi ini secara implisit
depends_on = [aws_iam_role_policy_attachment.lambda_exec]
}Dengan depends_on, kita memaksa Terraform menyelesaikan aws_iam_role_policy_attachment.lambda_exec sebelum membuat Lambda. Tanpa baris itu, aplikasi bisa saja berjalan normal di uji coba lokal, lalu intermittently gagal di produksi — sangat sulit didiagnosis karena terlihat acak.
Important
Aturan praktis: manfaatkan implicit dependency sebanyak mungkin, dan cadangkan depends_on untuk relasi yang benar-benar tak terlihat oleh graph. depends_on memperlambat plan (Terraform harus me-refresh semua resource yang terdaftar di dalamnya) dan mengunci urutan secara berlebihan, sehingga menghilangkan kesempatan eksekusi paralel. Terlalu banyak depends_on = graph yang rapuh dan lambat.
lifecycle)Setelah urutan aman, kita masuk ke perlindungan. Blok lifecycle adalah semacam sabuk pengaman untuk resource. Ia tidak mengubah cara resource dibuat, melainkan mengubah perilaku Terraform terhadap resource tersebut: kapan dibuat ulang, kapan boleh dihancurkan, dan perubahan apa yang harus diabaikan.
create_before_destroy = true — Rollout Tanpa DowntimeDefault Terraform saat mengubah resource yang tidak bisa di-update in-place (misalnya mengganti AMI atau mengubah atribut yang memaksa re-creation) adalah hancurkan dulu, baru buat (destroy-before-create). Konsekuensinya: ada jeda di mana resource tidak ada — alias downtime.
Untuk layanan yang berada di belakang load balancer atau punya titik alternatif, kita bisa membalik urutan dengan create_before_destroy = true: Terraform membuat resource baru terlebih dahulu, memastikannya sukses, baru menghancurkan yang lama.
variable "ami_id" {
type = string
default = "ami-0abcdef1234567890"
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
subnet_id = aws_subnet.app.id
lifecycle {
create_before_destroy = true
}
}Saat kalian mengganti var.ami_id dan menjalankan terraform apply, Terraform akan mencetak urutan yang berbeda dari biasanya:
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Creation complete after 12s [id=i-0new123]
aws_instance.web (new): Starting...
aws_instance.web: Destroying... [id=i-0old456]
aws_instance.web: Destruction complete after 5sPerhatikan urutannya: create → start → destroy. Justru urutan inilah yang membuat strategi zero-downtime bekerja untuk instance di balik target group — traffic otomatis pindah ke instance baru saat instance lama dihancurkan.
Caution
create_before_destroy tidak berlaku ajaib di semua resource. Resource yang memiliki constraint nama global unik (misalnya bucket S3, IAM role) tidak bisa punya dua instance dengan nama yang sama dalam waktu bersamaan — jadi mengganti atribut yang menempel di nama tetap menghasilkan error atau force replacement. create_before_destroy paling pas untuk resource yang identity-nya terpisah dari nama (instance ID, volume, dsb.).
prevent_destroy = true — Melindungi Resource VitalPernah menghapus stack dengan terraform destroy yang salah arah — dan database produksi ikut lenyap? prevent_destroy = true adalah pengaman terakhir untuk resource yang stateful dan tak tergantikan: database, storage bucket yang berisi data, dan sejenisnya.
resource "aws_db_instance" "primary" {
allocated_storage = 100
engine = "postgres"
engine_version = "16"
instance_class = "db.m5.large"
db_name = "app"
username = "app"
skip_final_snapshot = false
backup_retention_period = 30
lifecycle {
prevent_destroy = true
}
}Coba jalankan terraform destroy — Terraform akan menghentikan eksekusi dengan error berikut:
Error: Instance cannot be destroyed
on main.tf line 5, in resource "aws_db_instance" "primary":
5: resource "aws_db_instance" "primary" {
Resource aws_db_instance.primary has lifecycle.prevent_destroy set, but the
plan calls for this resource to be destroyed. To avoid this error and continue
with the plan, either disable lifecycle.prevent_destroy or adjust the scope of
the plan using the -target option.Pesan error di atas adalah fitur, bukan bug — ia memaksa manusia berhenti berpikir sebelum memusnahkan data. Untuk benar-benar menghapus resource ini nanti, prosesnya sengaja dibuat merepotkan: hapus blok lifecycle atau ubah nilai flag-nya menjadi false, lalu apply.
Warning
prevent_destroy hanya melindungi resource selama ia masih ada di kode. Jika kalian menghapus blok resource "aws_db_instance" "primary" dari file konfigurasi, Terraform tidak lagi tahu ada resource bernama itu, dan apply justru akan menghancurkannya tanpa error — karena resource itu sudah tidak terdeklarasi! Kombinasi prevent_destroy + terraform state rm adalah cara standar untuk "memisahkan diri" dari resource vital tanpa menghancurkannya (dibahas lebih dalam di episode 8).
ignore_changes — Mengabaikan Perubahan di Luar TerraformInfrastruktur di dunia nyata jarang statis. Ada tim lain yang menambahkan tag via console, Auto Scaling Group yang me-replace AMI, atau tooling yang me-restart instance. Perubahan-perubahan ini disebut drift. Saat terraform plan berjalan, perubahan di luar Terraform akan selalu terdeteksi — dan bisa memaksa Terraform untuk "menormalkan" kembali nilai yang sebenarnya sengaja diubah pihak lain.
ignore_changes memberi tahu Terraform: "jangan bandingkan atribut ini dengan state; biarkan apa adanya."
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "web-server"
Managed = "terraform"
}
lifecycle {
ignore_changes = [ami, tags]
}
}ami di-ignore: jika ada pipeline CI/CD yang meng-update AMI di luar Terraform, plan tidak akan memaksa revert.tags di-ignore: tim lain boleh menambah tag tanpa Terraform menghapusnya di plan berikutnya.Contoh lain yang sangat umum: sebuah EC2 instance yang di-join ke Auto Scaling Group, di mana ASG lah yang berhak mengatur kapasitas — bukan Terraform.
resource "aws_autoscaling_group" "workers" {
# ASG mengelola instance-nya sendiri
lifecycle {
ignore_changes = all
}
}Important
Untuk mengabaikan semua atribut, gunakan kata kunci all: ignore_changes = all. Gunakan dengan sangat hati-hati — efeknya resource menjadi "setengah lepas kendali" dari Terraform. Aturan sehat: ignore_changes hanya untuk atribut yang memang dikelola pihak lain, bukan untuk menutup-nutupi drift yang seharusnya ditangani.
lifecycle| Argument | Default | Fungsi |
|---|---|---|
create_before_destroy | false | Membuat resource baru sebelum menghancurkan yang lama (zero-downtime) |
prevent_destroy | false | Mencegah penghapusan resource (error saat plan memanggil destroy) |
ignore_changes | [] | Mengabaikan perubahan atribut tertentu (atau all) terhadap state |
replace_triggered_by | null | Memaksa replace resource saat nilai resource lain berubah (Terraform 1.2+) |
precondition / postcondition | null | Validasi kondisi sebelum/sesudah resource diterapkan (Terraform 1.2+) |
replace_triggered_by dan precondition/postcondition adalah fitur yang lebih baru dan layak dipelajari setelah kalian nyaman dengan tiga argument utama di atas.
Gabungkan semuanya dalam satu skenario nyata. Misalnya stack layanan web produksi:
create_before_destroy = true agar rollout aman.prevent_destroy = true agar tidak ada manusia yang bisa menghancurkannya dengan sekali terraform destroy.ignore_changes = all karena kapasitas dikelola oleh ASG, bukan Terraform.resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
subnet_id = aws_subnet.app.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_db_instance" "primary" {
engine = "postgres"
instance_class = "db.m5.large"
db_name = "app"
username = "app"
lifecycle {
prevent_destroy = true
}
}
resource "aws_autoscaling_group" "workers" {
lifecycle {
ignore_changes = [desired_capacity, tags]
}
}Tiga aturan ini adalah pola default yang dipakai tim produksi di banyak perusahaan besar: rolling update yang aman, data yang tak tergantikan dilindungi, dan noise drift yang dikelola secara eksplisit.
1. Membuat circular dependency dengan depends_on
Terraform memblokir dependensi melingkar (A menunggu B, B menunggu A). Error yang muncul adalah Cycle: aws_a.web, aws_b.web dan sulit dilacak saat config sudah besar. Aturan deteksi cepat: jika dua resource saling mereferensikan atribut satu sama lain, hampir pasti ada desain yang salah — pisahkan data-nya atau gunakan data source.
2. create_before_destroy pada resource bernama unik
Mengganti nama S3 bucket dengan create_before_destroy = true akan gagal karena bucket baru harus dibuat sebelum bucket lama dihancurkan, padahal namanya sama. Pada kasus ini, Terraform tetap akan melakukan replace tapi berpotensi error di cloud — selalu verifikasi constraint nama resource di dokumentasi provider.
3. prevent_destroy hanya melindungi resource yang masih terdeklarasi
Seperti dijelaskan di callout sebelumnya: menghapus blok resource dari kode = menghancurkan pada apply berikutnya, walau prevent_destroy pernah dipasang. Untuk menurunkan resource secara permanen, lakukan dengan sengaja dan berlapis: hapus prevent_destroy, apply, lalu destroy.
4. ignore_changes = all berlebihan
Mengabaikan semua perubahan membuat Terraform buta terhadap masalah nyata — misalnya instance diganti jenis di console tanpa sepengetahuan tim, atau security group diubah membuka port berbahaya. Batasi ignore_changes hanya pada atribut yang memang di luar kendali Terraform.
5. depends_on yang tidak perlu = plan lambat
depends_on memaksa Terraform me-refresh seluruh resource yang dirujuk bahkan saat tidak berubah. Di stack besar dengan ribuan resource, ini menambah detik bahkan menit pada setiap plan. Prune depends_on yang tidak dibutuhkan secara berkala.
Pada episode 7 ini kita telah memahami dua pilar pengendalian infrastruktur: dependency management — bagaimana Terraform menyusun urutan secara otomatis lewat implicit dependencies, dan kapan kita memaksakan urutan dengan depends_on — serta lifecycle rules yang menjadi sabuk pengaman resource: create_before_destroy untuk rollout tanpa downtime, prevent_destroy untuk melindungi resource stateful, dan ignore_changes untuk mengelola drift.
Kemampuan inilah yang membedakan konfigurasi "yang berjalan" dari konfigurasi "yang aman di tangan banyak orang". Urutan yang benar mencegah error acak, sedangkan lifecycle rules mencegah bencana yang tidak bisa diundur. Di tim produksi, kombinasi ketiganya adalah standar wajib sebelum sebuah stack boleh menyentuh environment production.
Namun ada satu pertanyaan besar yang belum terjawab: bagaimana jika infrastruktur kita sudah ada duluan di cloud, atau resource di kode harus dipindahkan ke tempat baru? Di episode 8 selanjutnya kita akan membahas Advanced State Manipulation — import untuk menghubungkan infrastruktur eksisting, moved block dan terraform state mv untuk refactoring tanpa destroy, serta terraform state rm, state list, dan state show untuk inspeksi state. Pastikan tetap semangat!