What Is Terraform?

  1. Introduction



  2. Key Characteristics



  3. Terraform Workflow


  4. Step Description Command
    1. Write Write .tf files describing infrastructure.
    2. Init Download providers and initialize the project. terraform init
    3. Plan Show what Terraform will create, modify, or destroy. terraform plan
    4. Apply Perform the actual infrastructure change. terraform apply


  5. A Simple Terraform Example

  6. This example creates an AWS EC2 instance.

    provider "aws" {
      region = "eu-central-1"
    }
    
    resource "aws_instance" "example" {
      ami           = "ami-12345678"
      instance_type = "t2.micro"
    }
    



  7. Terraform State



  8. Terraform Language (HCL)

  9. variable "project_name" {
      type = string
    }
    
    output "instance_id" {
      value = aws_instance.example.id
    }
    


  10. Common Terraform Terminology

  11. Term Description
    provider Plugin to manage a service (e.g., AWS, Azure).
    resource A cloud object (VM, network, DB, etc.).
    data source Read existing infrastructure.
    module Reusable grouping of Terraform files.
    state The file storing real-world infrastructure details.
    plan Preview of actions before applying changes.
    apply Run the actual changes.




Terraform Resource Lifecycle

  1. Introduction



  2. The lifecycle Block Structure
  3. resource "aws_instance" "example" {
      ami           = "ami-123456"
      instance_type = "t2.micro"
    
      lifecycle {
        create_before_destroy = false
        prevent_destroy       = false
        ignore_changes        = []
      }
    }
    



  4. create_before_destroy

  5. lifecycle {
      create_before_destroy = true
    }
    


  6. prevent_destroy

  7. lifecycle {
      prevent_destroy = true
    }
    



  8. ignore_changes

  9. lifecycle {
      ignore_changes = [
        tags,
        metadata,
      ]
    }
    



  10. Full Lifecycle Example
  11. resource "aws_instance" "server" {
      ami           = "ami-123456"
      instance_type = "t3.micro"
    
      lifecycle {
        create_before_destroy = true
        prevent_destroy       = false
        ignore_changes        = [
          tags["last_updated"],
          user_data,
        ]
      }
    }
    



  12. Lifecycle vs Depends On

  13. resource "aws_eip" "ip" {
      depends_on = [aws_instance.server]
    }
    


  14. Summary

  15. Setting Description Common Use Case
    create_before_destroy Create replacement before destroying old resource Zero downtime deployments
    prevent_destroy Protect resource from deletion Critical databases, S3 buckets
    ignore_changes Ignore drift on specific attributes Provider-controlled fields, timestamps




Terraform CLI (Command Line Interface)

  1. Introduction



  2. Basic Command Structure

  3. $ terraform <command> [options]
    

    $ terraform apply -auto-approve
    


  4. Essential Terraform CLI Commands

  5. Command Description Usage
    terraform init Initializes a working directory Download providers and set up backend
    terraform plan Show proposed changes Preview before apply
    terraform apply Apply the plan (create/update/destroy) Deploy infrastructure
    terraform destroy Destroy managed resources Cleanup infrastructure
    terraform validate Check module syntax Catches errors before running plan
    terraform fmt Format .tf files Enforce consistent style
    terraform providers Show used providers Dependency inspection
    terraform version Show Terraform version Debug/build info


  6. terraform init

  7. $ terraform init
    Initializing the backend...
    Initializing provider plugins...
    Terraform has been successfully initialized!
    


  8. terraform plan

  9. $ terraform plan
    Plan: 1 to add, 0 to change, 0 to destroy.
    


  10. terraform apply

  11. $ terraform apply
    Do you want to perform these actions?
      Enter a value: yes
    


  12. terraform destroy

  13. $ terraform destroy -auto-approve
    


  14. terraform validate

  15. $ terraform validate
    Success! The configuration is valid.
    


  16. terraform fmt

  17. $ terraform fmt
    


  18. terraform show

  19. $ terraform show
    
    # aws_instance.example:
    resource "aws_instance" "example" {
        ami           = "ami-123456"
        instance_type = "t2.micro"
    }
    


  20. terraform graph

  21. $ terraform graph | dot -Tpng > graph.png
    


  22. terraform state Commands

  23. Command Description
    state list List resources in state
    state show Show details for a resource
    state pull Download remote state
    state push Upload state manually
    state rm Remove resource from state


  24. terraform import

  25. $ terraform import aws_instance.example i-1234567890abcdef0
    


  26. Useful CLI Options

  27. -auto-approve        # Skip yes/no prompt
    -refresh=false       # Skip refreshing state
    -target=<address>    # Apply a specific resource
    -var name=value      # Pass variable
    -var-file=file.tfvars
    


  28. Summary of Terraform CLI

  29. Category Main Commands
    Setup init, fmt, validate
    Execution plan, apply, destroy
    State Management state, show, list, pull, push
    Migration import
    Debugging graph, providers, version




Writing Terraform Configuration

  1. What Does Terraform Configuration Mean?



  2. Terraform Files and Directory Structure

  3. .
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── terraform.tfvars
    


  4. The terraform Block

  5. terraform {
        required_version = ">= 1.5"
    
        required_providers {
            aws = {
                source  = "hashicorp/aws"
                version = "~> 5.0"
            }
        }
    
        backend "s3" {
            bucket = "my-tf-state"
            key    = "prod/terraform.tfstate"
            region = "eu-central-1"
        }
    }
    


  6. Provider Configuration

  7. provider "aws" {
        region = "eu-central-1"
    }
    


  8. Resources: The Core of Terraform

  9. resource "aws_instance" "my_server" {
        ami           = "ami-05f7491af5eef733a"
        instance_type = "t2.micro"
    
        tags = {
            Name = "DemoServer"
        }
    }
    
    resource "aws_eip" "my_ip" {
        instance = aws_instance.my_server.id
    }
    


  10. Variables

  11. variable "instance_type" {
        description = "EC2 instance type"
        type        = string
        default     = "t2.micro"
    }
    
    instance_type = var.instance_type
    


  12. Variable Files (.tfvars)

  13. instance_type = "t3.small"
    bucket_name   = "prod-assets-2025"
    
    terraform apply -var-file=prod.tfvars
    


  14. Outputs

  15. output "public_ip" {
        description = "Public IP of the instance"
        value       = aws_instance.my_server.public_ip
    }
    
    terraform output
    terraform output public_ip
    


  16. Locals

  17. locals {
        tags = {
            Environment = "production"
            Owner       = "Junzhe"
        }
    }
    
    resource "aws_s3_bucket" "bucket" {
        bucket = "my-demo-bucket-2025"
        tags   = local.tags
    }
    


  18. Data Sources

  19. data "aws_ami" "latest_amazon_linux" {
        owners      = ["amazon"]
        most_recent = true
    
        filter {
            name   = "name"
            values = ["amzn2-ami-hvm-*-x86_64-gp2"]
        }
    }
    
    resource "aws_instance" "server" {
        ami           = data.aws_ami.latest_amazon_linux.id
        instance_type = "t2.micro"
    }
    


  20. Modules

  21. module "vpc" {
        source = "terraform-aws-modules/vpc/aws"
        version = "~> 5.1"
    
        name = "prod-vpc"
        cidr = "10.0.0.0/16"
    }
    
    
    modules/
    └── ec2/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
    


  22. Expressions and Interpolation Syntax

  23. ami           = data.aws_ami.latest.id
    instance_type = var.instance_type
    tags          = local.tags
    
    timestamp()
    upper("hello")
    join("-", ["prod", "eu", "vpc"])
    


  24. Lifecycle Rules

  25. resource "aws_instance" "server" {
        ami           = var.ami
        instance_type = "t2.micro"
    
        lifecycle {
            prevent_destroy = true
        }
    }
    


  26. Putting Everything Together: A Complete Example

  27. terraform {
        required_providers {
            aws = {
                source = "hashicorp/aws"
                version = "~> 5.0"
            }
        }
    }
    
    provider "aws" {
        region = "eu-central-1"
    }
    
    variable "instance_type" {
        default = "t2.micro"
    }
    
    resource "aws_instance" "vm" {
        ami           = "ami-05f7491af5eef733a"
        instance_type = var.instance_type
    
        tags = {
            Name = "ExampleVM"
        }
    }
    
    resource "aws_eip" "ip" {
        instance = aws_instance.vm.id
    }
    
    output "public_ip" {
        value = aws_eip.ip.public_ip
    }
    
    terraform init
    terraform plan
    terraform apply
    



Using Terraform with AWS: An Introduction

  1. What Is AWS Terraform?



  2. Prerequisites for AWS Terraform

  3. aws configure
    
    ~/.aws/credentials
    ~/.aws/config
    


  4. AWS Provider Block

  5. terraform {
        required_providers {
            aws = {
                source  = "hashicorp/aws"
                version = "~> 5.0"
            }
        }
    }
    
    provider "aws" {
        region = "eu-central-1"
    }
    


  6. Deploying Your First AWS Resource: S3 Bucket

  7. resource "aws_s3_bucket" "example" {
        bucket = "junzhe-demo-bucket-12345"
    
        tags = {
            Purpose = "TerraformIntro"
        }
    }
    
    terraform init
    terraform apply
    


  8. Creating an EC2 Instance with Terraform

  9. resource "aws_instance" "web" {
        ami           = "ami-05f7491af5eef733a"
        instance_type = "t2.micro"
    
        tags = {
            Name = "TerraformWebServer"
        }
    }
    
    output "public_ip" {
        value = aws_instance.web.public_ip
    }
    


  10. Using Data Sources (AWS Example)

  11. data "aws_ami" "amazon_linux" {
        owners      = ["amazon"]
        most_recent = true
    
        filter {
            name   = "name"
            values = ["amzn2-ami-hvm-*-x86_64-gp2"]
        }
    }
    
    resource "aws_instance" "server" {
        ami           = data.aws_ami.amazon_linux.id
        instance_type = "t2.micro"
    }
    


  12. Managing AWS Networking (VPC, Subnets, SG)

  13. resource "aws_vpc" "main" {
        cidr_block = "10.0.0.0/16"
    }
    
    resource "aws_subnet" "public" {
        vpc_id                  = aws_vpc.main.id
        cidr_block              = "10.0.1.0/24"
        map_public_ip_on_launch = true
    }
    
    resource "aws_security_group" "web_sg" {
        name        = "web-sg"
        description = "Allow HTTP"
        vpc_id      = aws_vpc.main.id
    
        ingress {
            from_port   = 80
            to_port     = 80
            protocol    = "tcp"
            cidr_blocks = ["0.0.0.0/0"]
        }
    }
    


  14. IAM with Terraform (Users, Roles, Policies)

  15. resource "aws_iam_user" "developer" {
        name = "junzhe-dev"
    }
    
    resource "aws_iam_access_key" "developer_key" {
        user = aws_iam_user.developer.name
    }
    
    output "dev_access_key" {
        value = aws_iam_access_key.developer_key.id
    }
    


  16. S3 + IAM Example (Realistic Use Case)

  17. resource "aws_s3_bucket" "app_bucket" {
        bucket = "terraform-app-bucket-2025"
    }
    
    data "aws_iam_policy_document" "rw_access" {
        statement {
            actions   = ["s3:*"]
            resources = [
                aws_s3_bucket.app_bucket.arn,
                "${aws_s3_bucket.app_bucket.arn}/*"
            ]
        }
    }
    
    resource "aws_iam_policy" "app_bucket_policy" {
        name   = "AppBucketRW"
        policy = data.aws_iam_policy_document.rw_access.json
    }
    


  18. Using AWS Modules

  19. module "vpc" {
        source  = "terraform-aws-modules/vpc/aws"
        version = "5.1.0"
    
        name = "production-vpc"
        cidr = "10.1.0.0/16"
    
        azs             = ["eu-central-1a", "eu-central-1b"]
        public_subnets  = ["10.1.1.0/24", "10.1.2.0/24"]
        private_subnets = ["10.1.3.0/24", "10.1.4.0/24"]
    }
    


  20. Remote State Backend for AWS Projects (Recommended)

  21. terraform {
        backend "s3" {
            bucket         = "my-terraform-state-prod"
            key            = "global/state.tfstate"
            region         = "eu-central-1"
            dynamodb_table = "terraform-locks"
            encrypt        = true
        }
    }
    


  22. Common AWS Terraform Patterns




Understanding the terraform init Command

  1. What Does terraform init Do?



  2. What Happens in the Working Directory?



  3. Basic Usage: terraform init



  4. How terraform init Handles Providers



  5. How terraform init Handles Modules



  6. Backends and terraform init



  7. Useful Options for terraform init



  8. Idempotency: Re-running terraform init



  9. Putting It All Together: Example Session




Understanding the terraform Block in Terraform

  1. What Is the terraform Block?



  2. The General Structure of the terraform Block



  3. required_version — Terraform CLI Version Constraint



  4. required_providers — Provider Requirements


  5. backend — Terraform State Storage



  6. Less Common but Useful Settings

    1. provider_meta (rare)
    2. terraform {
          provider_meta "aws" {
              module_name = "custom-aws-module"
          }
      }
      

    3. experiments (very rare)
    4. terraform {
          experiments = [module_variable_optional_attrs]
      }
      

    5. cloud block (Terraform Cloud/Enterprise)
    6. terraform {
          cloud {
              organization = "mycompany"
      
              workspaces {
                  name = "production"
              }
          }
      }
      



  7. Putting It All Together — Complete Example
  8. terraform {
        required_version = "~> 1.5"
    
        required_providers {
            aws = {
                source  = "hashicorp/aws"
                version = "~> 5.0"
            }
            kubernetes = {
                source  = "hashicorp/kubernetes"
                version = "~> 2.20"
            }
        }
    
        backend "s3" {
            bucket = "my-terraform-state-bucket"
            key    = "envs/prod/state.tfstate"
            region = "eu-central-1"
            encrypt = true
        }
    }
    



Understanding the provider Block in Terraform

  1. What Is the provider Block?



  2. The Structure of a provider Block



  3. Example: AWS Provider Configuration



  4. Example: Google Cloud Provider
  5. provider "google" {
        project = "my-gcp-project"
        region  = "us-central1"
        zone    = "us-central1-a"
    }
    


  6. Multiple Provider Configurations



  7. Provider Inheritance Rule



  8. provider Blocks vs. required_providers



  9. Passing Variables into Provider Configurations



  10. Debugging Provider Issues



  11. Complete Example
  12. terraform {
        required_providers {
            aws = {
                source  = "hashicorp/aws"
                version = "~> 5.0"
            }
        }
    }
    
    provider "aws" {
        region = "eu-central-1"
    
        default_tags {
            tags = {
                env = "production"
            }
        }
    }
    
    resource "aws_s3_bucket" "example" {
        bucket = "my-s3-bucket-demo-12345"
    }
    



Understanding the resource Block in Terraform

  1. What Is a resource Block?



  2. The Structure of a resource Block



  3. Understanding Resource Types



  4. Example 1: AWS EC2 Instance
  5. resource "aws_instance" "web" {
        ami           = "ami-0ff8a91507f77f867"
        instance_type = "t3.micro"
    
        tags = {
            Name = "WebServer"
        }
    }
    


  6. Resource Arguments vs. Attributes



  7. Lifecycles for Resources (lifecycle Block)



  8. Meta-Arguments in Resource Blocks



  9. Resource Dependencies



  10. Importing Existing Resources



  11. Destroying Resources



  12. Complete Example Project



Understanding the variable Block in Terraform

  1. What Is a variable Block?



  2. Basic Structure of a variable Block



  3. Example: A Simple String Variable


  4. Supported Variable Types

  5. Type Description Example
    string Text value "hello"
    number Numeric value 42
    bool true / false true
    list(type) Ordered list ["a", "b"]
    map(type) Key-value map { env = "prod" }
    set(type) Unique unordered list set(["a", "b"])
    object({}) Structured key/value object { name = string, size = number }
    tuple([]) Fixed-length list of various types [string, number]


  6. Example: List Variable
  7. variable "availability_zones" {
        type = list(string)
        default = ["eu-central-1a", "eu-central-1b"]
    }
    
    subnet_id = var.availability_zones[0]
    


  8. Example: Map Variable
  9. variable "instance_tags" {
        type = map(string)
        default = {
            project = "terraform-demo"
            owner   = "junzhe"
        }
    }
    
    tags = var.instance_tags
    


  10. Example: Object Variable
  11. variable "server_config" {
        type = object({
            size = string
            count = number
            tags = map(string)
        })
    }
    
    instance_type = var.server_config.size
    tags          = var.server_config.tags
    


  12. Using Variables in a Configuration

  13. provider "aws" {
        region = var.region
    }
    


  14. You can set variable values from many sources



  15. Sensitive Variables



  16. Default Values



  17. Validation Rules



  18. Complete Example
  19. variable "region" {
        type        = string
        default     = "eu-central-1"
        description = "The AWS region"
    }
    
    variable "tags" {
        type = map(string)
        default = {
            project = "demo"
            owner   = "junzhe"
        }
    }
    
    resource "aws_s3_bucket" "example" {
        bucket = "my-demo-bucket"
        tags   = var.tags
    }
    
    provider "aws" {
        region = var.region
    }
    



Understanding the output Block in Terraform

  1. What Is an output Block?



  2. Basic Structure of an output Block
  3. output "NAME" {
        value       = EXPRESSION
        description = "Human-readable text"
        sensitive   = BOOLEAN
    }
    


  4. Example: Simple Output
  5. output "bucket_name" {
        value = aws_s3_bucket.my_bucket.id
    }
    
    
    bucket_name = "my-bucket-123"
    


  6. When Are Outputs Shown?



  7. The value Argument



  8. Marking Outputs as Sensitive



  9. Using output in Modules



  10. Using Outputs for Cross-Project Sharing



  11. Output Format Options: terraform output



  12. Complex Output Values



  13. Conditional Outputs
  14. output "endpoint" {
        value = var.is_prod
            ? aws_lb.prod_lb.dns_name
            : aws_lb.dev_lb.dns_name
    }
    


  15. Complete Example
  16. resource "aws_instance" "web" {
        ami           = "ami-0ff8a91507f77f867"
        instance_type = "t3.micro"
    }
    
    output "public_ip" {
        value       = aws_instance.web.public_ip
        description = "The public IP of the web server"
    }
    
    output "ssh_command" {
        value = "ssh ec2-user@${aws_instance.web.public_ip}"
    }
    
    public_ip = "54.22.13.91"
    ssh_command = "ssh ec2-user@54.22.13.91"
    



Understanding the locals Block in Terraform

  1. What Is a locals Block?



  2. Basic Structure of a locals Block
  3. locals {
        NAME = EXPRESSION
        NAME2 = EXPRESSION
    }
    


  4. Referencing Local Values



  5. Example: Avoiding Duplication
  6. 
    locals {
      common_tags = {
        project = "demo"
        owner   = "junzhe"
      }
    }
    
    resource "aws_s3_bucket" "bucket1" {
      bucket = "bucket-one"
      tags   = local.common_tags
    }
    
    resource "aws_s3_bucket" "bucket2" {
      bucket = "bucket-two"
      tags   = local.common_tags
    }
    


  7. Local Values Can Use Expressions



  8. Example: Complex Expression



  9. Locals with Maps
  10. locals {
        app_ports = {
            http = 80
            https = 443
            metrics = 9100
        }
    }
    
    output "http_port" {
        value = local.app_ports["http"]
    }
    


  11. Locals with Lists
  12. locals {
        zones = [
            "${var.region}a",
            "${var.region}b",
            "${var.region}c"
        ]
    }
    
    resource "aws_subnet" "subnet1" {
        availability_zone = local.zones[0]
    }
    


  13. Locals with Resource References



  14. Example: Combining Map + Logic
  15. 
    locals {
        configs = {
            dev = { instance = "t3.micro", count = 1 }
            prod = { instance = "t3.large", count = 3 }
        }
    
        active_config = local.configs[var.env]
    }
    
    resource "aws_instance" "web" {
        instance_type = local.active_config.instance
        count         = local.active_config.count
    }
    


  16. Using Multiple locals Blocks



  17. Complete Practical Example
  18. variable "env" {
        type    = string
        default = "dev"
    }
    
    locals {
        common_tags = {
            project = "terraform-demo"
            owner   = "junzhe"
        }
    
        full_name = "${var.env}-webserver"
    }
    
    resource "aws_instance" "web" {
        ami           = "ami-0ff8a91507f77f867"
        instance_type = "t3.micro"
        tags          = local.common_tags
    }
    
    output "name" {
        value = local.full_name
    }
    



Understanding the import Block in Terraform

  1. What Is the import Block?



  2. Basic Structure of an import Block
  3. import {
        to = RESOURCE_ADDRESS
        id = PROVIDER_RESOURCE_ID
    }
    


  4. Example: Importing an AWS S3 Bucket



  5. Full Lifecycle of Import Using the import Block



  6. Using Terraform to Generate Configuration After Import



  7. How Import Works Internally



  8. Importing Multiple Resources
  9. import {
        to = aws_iam_role.app_role
        id = "my-app-role"
    }
    
    import {
        to = aws_iam_policy.read_policy
        id = "arn:aws:iam::123456789012:policy/ReadPolicy"
    }
    
    import {
        to = aws_s3_bucket.data
        id = "data-bucket"
    }
    


  10. Importing Nested or Child Resources

  11. module "network" {
        source = "./network"
    }
    
    import {
        to = module.network.aws_vpc.main
        id = "vpc-123456"
    }
    


  12. Example: Importing an Azure Resource
  13. resource "azurerm_resource_group" "main" {
        name = "rg-demo"
        location = "westeurope"
    }
    
    import {
        to = azurerm_resource_group.main
        id = "/subscriptions/xxx/resourceGroups/rg-demo"
    }
    


  14. Reference: Full Syntax Options
  15. import {
        to         = RESOURCE_ADDRESS
        id         = IDENTIFIER
        provider   = PROVIDER_ALIAS   # optional
    }
    


  16. Common Import Errors


  17. Import vs Creating New Resources


  18. When Should You Use the import Block?



  19. Complete Practical Example




Understanding the module Block in Terraform


  1. A module block is how you call or reuse Terraform configurations stored in another directory or registry.


  2. Basic Structure of a module Block
  3. module "NAME" {
        source = "SOURCE_LOCATION"
    
        # input variables
        variable1 = VALUE
        variable2 = VALUE
    }
    


  4. Module Source Types

  5. Source Type Example Description
    Local path ./modules/vpc Call module stored locally
    Git Repository git::https://github.com/user/repo.git Load modules directly from Git
    Terraform Registry terraform-aws-modules/vpc/aws Official or community modules
    Private Registry app.terraform.io/org/module/aws Enterprise patterns
    HTTP URL https://example.com/module.zip Download module archive


  6. Example: Local Module
  7. module "network" {
        source = "./modules/network"
    
        cidr_block = "10.0.0.0/16"
        env        = var.env
    }
    
    modules/
        network/
            main.tf
            variables.tf
            outputs.tf
    


  8. Example: Using a Public Registry Module
  9. module "vpc" {
        source  = "terraform-aws-modules/vpc/aws"
        version = "5.5.0"
    
        name = "demo"
        cidr = "10.0.0.0/16"
    }
    


  10. Passing Input Variables to a Module



  11. Consuming Module Outputs



  12. Using Multiple Modules
  13. module "network" { ... }
    module "database" { ... }
    module "frontend" { ... }
    module "backend" { ... }
    


  14. Meta-Arguments in Modules



  15. Versioning Modules
  16. module "vpc" {
        source  = "terraform-aws-modules/vpc/aws"
        version = "~> 5.5"
    }
    


  17. Complete Real-World Example

  18. module "network" {
        source  = "./modules/network"
        cidr    = "10.0.0.0/16"
    }
    
    module "compute" {
        source        = "./modules/compute"
        subnet_ids    = module.network.subnet_ids
        instance_type = "t3.micro"
        count         = 2
    }
    
    output "instance_ips" {
        value = module.compute.instance_ips
    }
    



Where to Put Each Terraform Block & Recommended File Structure

  1. How Terraform Reads Files (Very Important Concept)



  2. Typical Recommended File Layout in a Small Project



  3. Where to Put the terraform Block



  4. Where to Put provider Blocks



  5. Where to Put variable Blocks



  6. Where to Put locals Blocks



  7. Where to Put resource Blocks



  8. Where to Put output Blocks



  9. Where to Put module Blocks (Calling Modules)



  10. Where to Put import Blocks



  11. Recommended Structure for Child Modules



  12. Putting It All Together: Example Full Layout
  13. project-root/
    ├── versions.tf      # terraform { ... }
    ├── providers.tf     # provider "aws" { ... }
    ├── variables.tf     # variable "env" { ... }, etc.
    ├── locals.tf        # locals { ... }
    ├── main.tf          # top-level modules & maybe a few core resources
    ├── network.tf       # VPC, subnets, routes
    ├── security.tf      # security groups, IAM
    ├── compute.tf       # EC2, autoscaling
    ├── storage.tf       # S3, RDS, EBS
    ├── outputs.tf       # output "..." { ... }
    ├── import.tf        # optional import { ... } blocks
    └── modules/
        ├── network/
        │   ├── main.tf
        │   ├── variables.tf
        │   ├── outputs.tf
        │   └── locals.tf
        └── app/
            ├── main.tf
            ├── variables.tf
            ├── outputs.tf
            └── locals.tf