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
FileFungsiIsiKapan Pisah
main.tfResource utamaSemua resource atau entry point ke moduleSelalu ada
variables.tfDeklarasi inputSemua variable blockSelalu ada
outputs.tfDeklarasi outputSemua output blockSelalu ada
providers.tfKonfigurasi providerterraform {} dan provider {} blocksSelalu ada
backend.tfRemote statebackend {} di dalam terraform {}Saat pakai remote state
locals.tfLocal valuesSemua locals {} blockSaat locals cukup banyak
data.tfData sourcesSemua data blockSaat data sources banyak
networking.tfKomponen jaringanVPC, subnet, SG, routingSaat resource banyak
compute.tfKomponen computeEC2, ASG, LambdaSaat resource banyak
database.tfKomponen databaseRDS, ElastiCache, DynamoDBSaat resource banyak
iam.tfKomponen IAMRoles, policies, attachmentsSaat 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:#2e7d32
my-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
FileCommit?Alasan
*.tf✅ YaKonfigurasi utama, harus di-share
.terraform.lock.hcl✅ YaMenjamin versi provider konsisten
*.tfvars (non-secret)✅ YaNilai variable non-sensitif
.gitignore✅ YaAturan exclude
terraform.tfstate❌ TidakMengandung secret dan data sensitif
terraform.tfstate.backup❌ TidakBackup dari state file
.terraform/❌ TidakBinary provider, bisa regenerate
*.tfplan❌ TidakBisa mengandung sensitive value
*.tfvars (dengan secret)❌ TidakAPI 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 .gitignore kamu mengeksklusi semua *.tfvars, pastikan ada mekanisme lain untuk menyimpan nilai variable — misalnya environment variable (TF_VAR_*), .tfvars yang disimpan di secret manager, atau terraform.tfvars.example sebagai 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/
PolaCocok untukKelebihanKekurangan
Single directoryProyek kecil, 1 orangSimpel, mudah dimengertiTidak scalable
Multi-environmentTim kecil, beberapa envIsolasi per environmentDuplikasi config
Per-layerInfrastruktur besarDependency jelas, bisa dikerjakan paralelKompleks untuk tim kecil
Per-teamOrganisasi besarOwnership jelas, independensi timButuh koordinasi antar tim
Per-serviceMicroservicesIsolasi tinggi, deploy independenBanyak 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.tf lebih baik dari satu main.tf raksasa.
  • 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.

← Sebelumnya: CLI   Berikutnya: Init →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact