Struktur Direktori #
Tidak ada aturan baku tentang bagaimana kamu harus mengatur file Terraform — tapi ada konvensi yang diterima luas dan terbukti membantu proyek tetap maintainable seiring bertambahnya kompleksitas. Struktur yang baik membuat kolaborasi lebih mudah, onboarding lebih cepat, dan debugging lebih terarah. Bagian ini memandu konvensi penamaan file, struktur dari proyek kecil hingga besar, file mana yang boleh di-commit, dan anti-pattern yang harus dihindari.
Konvensi Penamaan File #
Terraform membaca semua file .tf dalam satu direktori secara bersamaan — urutan file tidak penting, tapi nama file yang konsisten membantu navigasi. Anggap konvensi ini sebagai “table of contents” untuk proyekmu.
flowchart LR
subgraph "File Standar"
A["main.tf\nResource utama"]
B["variables.tf\nDeklarasi input"]
C["outputs.tf\nDeklarasi output"]
D["providers.tf\nProvider config"]
end
subgraph "File Opsional"
E["versions.tf\nVersion constraint"]
F["backend.tf\nRemote state config"]
G["locals.tf\nLocal values"]
H["data.tf\nData sources"]
end
subgraph "File Per-Komponen"
I["networking.tf\nVPC, subnet, routing"]
J["compute.tf\nEC2, ASG"]
K["database.tf\nRDS, ElastiCache"]
L["iam.tf\nRoles, policies"]
end
style A fill:#e3f2fd,stroke:#1565c0
style B fill:#e3f2fd,stroke:#1565c0
style C fill:#e3f2fd,stroke:#1565c0
style D fill:#e3f2fd,stroke:#1565c0
style I fill:#fff3e0,stroke:#e65100
style J fill:#fff3e0,stroke:#e65100
style K fill:#fff3e0,stroke:#e65100
style L fill:#fff3e0,stroke:#e65100| File | Fungsi | Isi | Kapan Pisah |
|---|---|---|---|
main.tf | Resource utama | Semua resource atau entry point ke module | Selalu ada |
variables.tf | Deklarasi input | Semua variable block | Selalu ada |
outputs.tf | Deklarasi output | Semua output block | Selalu ada |
providers.tf | Konfigurasi provider | terraform {} dan provider {} blocks | Selalu ada |
backend.tf | Remote state | backend {} di dalam terraform {} | Saat pakai remote state |
locals.tf | Local values | Semua locals {} block | Saat locals cukup banyak |
data.tf | Data sources | Semua data block | Saat data sources banyak |
networking.tf | Komponen jaringan | VPC, subnet, SG, routing | Saat resource banyak |
compute.tf | Komponen compute | EC2, ASG, Lambda | Saat resource banyak |
database.tf | Komponen database | RDS, ElastiCache, DynamoDB | Saat resource banyak |
iam.tf | Komponen IAM | Roles, policies, attachments | Saat resource banyak |
Terraform tidak peduli nama file apa yang kamu gunakan — semua .tf di satu direktori di-merge menjadi satu konfigurasi. Konvensi penamaan ini murni untuk membantu manusia bernavigasi, bukan untuk Terraform.Struktur Proyek Kecil #
Untuk proyek dengan satu environment dan resource yang tidak terlalu banyak, satu direktori sudah cukup. Jangan over-engineer struktur untuk proyek sederhana — mulai dengan yang minimal, refactor saat kompleksitas bertambah.
my-infrastructure/
├── main.tf # Resource utama (EC2, VPC, RDS, dll.)
├── variables.tf # Deklarasi variable
├── outputs.tf # Output values
├── providers.tf # Konfigurasi provider
├── terraform.tfvars # Nilai variable (jangan commit jika ada secret)
├── backend.tf # Remote state config (opsional)
├── .terraform.lock.hcl # Lock file provider (selalu commit)
└── .gitignore # File yang tidak boleh di-commit
# providers.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# variables.tf
variable "aws_region" {
description = "AWS region untuk deploy resource"
type = string
default = "ap-southeast-1"
}
variable "environment" {
description = "Nama environment (dev, staging, production)"
type = string
}
Struktur Proyek Multi-Environment #
Proyek yang punya beberapa environment (dev, staging, production) membutuhkan struktur yang lebih terorganisir. Pola yang paling umum adalah memisahkan setiap environment ke direktori terpisah, masing-masing dengan state sendiri.
flowchart TD
subgraph "Struktur Multi-Environment"
ROOT["my-infrastructure/"]
MOD["modules/\nReusable components"]
MOD_VPC["modules/vpc/"]
MOD_WEB["modules/web-server/"]
ENV["environments/"]
DEV["environments/dev/\nConfig + State dev"]
STG["environments/staging/\nConfig + State staging"]
PRD["environments/production/\nConfig + State production"]
end
ROOT --> MOD
ROOT --> ENV
MOD --> MOD_VPC
MOD --> MOD_WEB
ENV --> DEV
ENV --> STG
ENV --> PRD
DEV -->|"module \"vpc\" { source }"| MOD_VPC
STG -->|"module \"vpc\" { source }"| MOD_VPC
PRD -->|"module \"vpc\" { source }"| MOD_VPC
style DEV fill:#e3f2fd,stroke:#1565c0
style STG fill:#fff3e0,stroke:#e65100
style PRD fill:#ffebee,stroke:#c62828
style MOD fill:#e8f5e9,stroke:#2e7d32my-infrastructure/
├── modules/ # Modul yang bisa di-reuse
│ ├── vpc/
│ │ ├── main.tf # Resource VPC, subnet, routing
│ │ ├── variables.tf # Input untuk module VPC
│ │ └── outputs.tf # Output dari module VPC
│ └── web-server/
│ ├── main.tf # Resource EC2, SG, ALB
│ ├── variables.tf # Input untuk module web-server
│ └── outputs.tf # Output dari module web-server
│
├── environments/
│ ├── dev/ # Environment development
│ │ ├── main.tf # Panggil modul dengan config dev
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ ├── terraform.tfvars # Nilai variable untuk dev
│ │ └── backend.tf # Remote state untuk dev
│ │
│ ├── staging/ # Environment staging
│ │ ├── main.tf
│ │ ├── terraform.tfvars # Nilai variable untuk staging
│ │ └── ...
│ │
│ └── production/ # Environment production
│ ├── main.tf
│ ├── terraform.tfvars # Nilai variable untuk production
│ └── ...
│
└── .terraform.lock.hcl
Dengan struktur ini, setiap environment punya state-nya sendiri dan perubahan di dev tidak mempengaruhi production. Setiap direktori environment memanggil module yang sama tapi dengan konfigurasi yang berbeda.
# environments/dev/main.tf — memanggil module dengan config dev
module "vpc" {
source = "../../modules/vpc"
vpc_cidr = "10.0.0.0/16"
environment = "dev"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
# environments/production/main.tf — memanggil module yang sama
module "vpc" {
source = "../../modules/vpc"
vpc_cidr = "10.1.0.0/16" # CIDR berbeda
environment = "production"
public_subnets = ["10.1.1.0/24", "10.1.2.0/24"]
}
File yang Harus Di-commit dan Yang Tidak #
Ini salah satu hal yang paling sering membingungkan developer baru. Kesalahan di sini bisa berakibat fatal — commit state file berarti membocorkan semua secret, tidak commit lock file berarti semua orang bisa dapat versi provider berbeda.
flowchart TD
A["File di proyek Terraform"] --> B{"Harus di-commit?"}
B -->|"Ya"| C["*.tf — semua konfigurasi"]
B -->|"Ya"| D[".terraform.lock.hcl — provider lock"]
B -->|"Ya"| E["*.tfvars — jika TANPA secret"]
B -->|"Ya"| F[".gitignore — exclude rules"]
B -->|"Tidak"| G["terraform.tfstate — mengandung secret"]
B -->|"Tidak"| H["terraform.tfstate.backup — backup state"]
B -->|"Tidak"| I[".terraform/ — provider binary cache"]
B -->|"Tidak"| J["*.tfplan — bisa mengandung sensitive value"]
B -->|"Tidak"| K["*.tfvars — jika mengandung secret"]
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#e8f5e9,stroke:#2e7d32
style E fill:#e8f5e9,stroke:#2e7d32
style F fill:#e8f5e9,stroke:#2e7d32
style G fill:#ffebee,stroke:#c62828
style H fill:#ffebee,stroke:#c62828
style I fill:#ffebee,stroke:#c62828
style J fill:#ffebee,stroke:#c62828
style K fill:#ffebee,stroke:#c62828| File | Commit? | Alasan |
|---|---|---|
*.tf | ✅ Ya | Konfigurasi utama, harus di-share |
.terraform.lock.hcl | ✅ Ya | Menjamin versi provider konsisten |
*.tfvars (non-secret) | ✅ Ya | Nilai variable non-sensitif |
.gitignore | ✅ Ya | Aturan exclude |
terraform.tfstate | ❌ Tidak | Mengandung secret dan data sensitif |
terraform.tfstate.backup | ❌ Tidak | Backup dari state file |
.terraform/ | ❌ Tidak | Binary provider, bisa regenerate |
*.tfplan | ❌ Tidak | Bisa mengandung sensitive value |
*.tfvars (dengan secret) | ❌ Tidak | API key, password, dll. |
.gitignore yang Direkomendasikan
#
# .gitignore untuk proyek Terraform
# Local .terraform directories
**/.terraform/*
# .tfstate files — mengandung data sensitif
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude semua .tfvars files yang might contain sensitive data
*.tfvars
*.tfvars.json
# Override files (local customizations)
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Saved plan files
*.tfplan
# Lock file TIDAK di-exclude — ini harus di-commit
!.terraform.lock.hcl
# Jika punya .tfvars non-secret yang ingin di-commit
!example.tfvars
Jika.gitignorekamu mengeksklusi semua*.tfvars, pastikan ada mekanisme lain untuk menyimpan nilai variable — misalnya environment variable (TF_VAR_*),.tfvarsyang disimpan di secret manager, atauterraform.tfvars.examplesebagai template tanpa nilai sebenarnya.
Pola Struktur Lanjutan #
Saat proyek tumbuh, struktur yang lebih spesifik bisa membantu mengelola kompleksitas.
Pemisahan Berdasarkan Layer #
infrastructure/
├── layers/
│ ├── networking/ # VPC, subnets, routing — di-apply duluan
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── backend.tf
│ │
│ ├── security/ # IAM, KMS, Security Groups
│ │ ├── main.tf
│ │ └── ...
│ │
│ ├── compute/ # EC2, ECS, Lambda
│ │ ├── main.tf
│ │ └── ...
│ │
│ └── data/ # RDS, ElastiCache, S3
│ ├── main.tf
│ └── ...
Pemisahan Berdasarkan Team #
infrastructure/
├── platform/ # Tim platform yang mengelola
│ ├── vpc/
│ ├── eks-cluster/
│ └── shared-services/
│
├── application-a/ # Tim A yang mengelola
│ ├── rds/
│ ├── s3/
│ └── lambda/
│
└── application-b/ # Tim B yang mengelola
├── ecs/
└── cloudfront/
Pemisahan Berdasarkan Service #
infrastructure/
├── services/
│ ├── api-gateway/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── user-service/
│ │ ├── main.tf
│ │ └── ...
│ ├── payment-service/
│ │ ├── main.tf
│ │ └── ...
│ └── notification-service/
│ ├── main.tf
│ └── ...
│
└── shared/
├── vpc/
├── dns/
└── monitoring/
| Pola | Cocok untuk | Kelebihan | Kekurangan |
|---|---|---|---|
| Single directory | Proyek kecil, 1 orang | Simpel, mudah dimengerti | Tidak scalable |
| Multi-environment | Tim kecil, beberapa env | Isolasi per environment | Duplikasi config |
| Per-layer | Infrastruktur besar | Dependency jelas, bisa dikerjakan paralel | Kompleks untuk tim kecil |
| Per-team | Organisasi besar | Ownership jelas, independensi tim | Butuh koordinasi antar tim |
| Per-service | Microservices | Isolasi tinggi, deploy independen | Banyak direktori dan state |
Anti-Pattern yang Harus Dihindari #
ANTI-PATTERN 1: Semua resource dalam satu file besar
─────────────────────────────────────────────────────
# main.tf dengan 1000+ baris mencakup VPC, EC2, RDS, IAM, dll.
# → Sulit dibaca, sulit di-maintain, konflik merge yang sering
# BENAR: Pisah berdasarkan logical grouping
# networking.tf — VPC, subnet, routing
# compute.tf — EC2, auto scaling
# database.tf — RDS, ElastiCache
# iam.tf — Roles, policies
ANTI-PATTERN 2: Hardcode nilai di dalam resource
─────────────────────────────────────────────────────
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890" # ✗ hardcode
instance_type = "t3.micro" # ✗ hardcode
}
# BENAR: Gunakan variable
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
}
ANTI-PATTERN 3: Tidak ada struktur untuk multi-environment
─────────────────────────────────────────────────────
# Semua environment dalam satu direktori dengan variable untuk bedakan
# → State tercampur, berbahaya
# BENAR: Direktori terpisah per environment
# environments/dev/, environments/staging/, environments/production/
# Masing-masing dengan state-nya sendiri
ANTI-PATTERN 4: Module di direktori yang sama dengan root config
─────────────────────────────────────────────────────
# .
# ├── main.tf # Root config
# ├── variables.tf
# └── my-module/ # ✗ Module di root yang sama
# ├── main.tf
# BENAR: Module di direktori terpisah
# .
# ├── modules/
# │ └── my-module/
# ├── environments/
# │ └── dev/
# │ └── main.tf # module "x" { source = "../../modules/my-module" }
Ringkasan #
- Konvensi file:
main.tf,variables.tf,outputs.tf,providers.tf— nama yang konsisten membuat navigasi lebih mudah untuk semua anggota tim.- Pisah berdasarkan logical grouping untuk file yang besar —
networking.tf,compute.tf,database.tflebih baik dari satumain.tfraksasa.- Multi-environment = direktori terpisah — jangan gunakan satu direktori untuk semua environment, state akan tercampur.
- Commit
.terraform.lock.hcl, jangan commit.terraform/dan*.tfstate.- Hati-hati dengan
.tfvars— jika mengandung secret, jangan di-commit. Gunakan environment variable atau secrets manager.- Modul di direktori
modules/untuk konfigurasi yang di-reuse antar environment.- Pilih pola struktur berdasarkan skala proyek — single directory untuk kecil, multi-environment untuk tim kecil, per-layer/per-team untuk organisasi besar.
- Mulai dengan yang sederhana, refactor saat kompleksitas bertambah — jangan over-engineer di awal.