📌 Key Takeaways
- Terraform is used by 80%+ of DevOps teams managing multi-cloud infrastructure
- IaC proficiency adds ₹3-6 LPA salary premium for DevOps engineers in Bangalore
- HashiCorp Terraform Associate is among the top 10 most-requested certifications in Indian cloud job postings
- Mastering Terraform opens doors to AWS, Azure, GCP and 1,800+ other providers
- Average salary hike after Terraform training: 65% (Thick Brain placement data)
Infrastructure as Code (IaC) has fundamentally changed how cloud infrastructure is built and managed. Instead of clicking through cloud consoles or running one-off CLI commands, engineers define infrastructure — virtual machines, networks, databases, load balancers — as versioned, reviewable, reproducible code. Terraform, HashiCorp's open-source IaC tool, has become the industry standard for this practice. In 2026, the ability to write production-quality Terraform code is a non-negotiable skill for every DevOps engineer and cloud architect.
📊 Terraform Market Snapshot — 2026
What is Terraform?
Terraform is an open-source Infrastructure as Code tool created by HashiCorp. It lets you define infrastructure resources (servers, databases, networking, DNS, access policies) in HCL (HashiCorp Configuration Language) — a declarative, human-readable language — then apply those definitions to create and manage real infrastructure across any cloud or on-premise environment.
The core Terraform workflow is three steps: Write (define resources in .tf files), Plan (terraform plan shows exactly what will be created/changed/destroyed), Apply (terraform apply makes it happen). This preview-before-apply approach makes infrastructure changes safe and auditable.
Why Terraform Over Cloud-Native IaC Tools?
Terraform vs CloudFormation (AWS)
AWS CloudFormation is AWS-only. Terraform works across 1,800+ providers. If your organisation uses AWS today but might adopt Azure or GCP services tomorrow, Terraform lets you use the same workflow and language for everything. For multi-cloud strategies, Terraform is almost always the preferred choice.
Terraform vs Ansible
Ansible is a configuration management tool (great for installing software, configuring OS settings, managing application deployments). Terraform is an infrastructure provisioning tool (creating and managing cloud resources). They are complementary — most production environments use Terraform to provision infrastructure and Ansible to configure what runs on it.
Terraform vs Pulumi
Pulumi lets you write IaC in general-purpose languages (Python, TypeScript, Go). Terraform uses its own HCL language. HCL is simpler to learn and Terraform has a larger community and ecosystem — making it the dominant choice in the industry despite Pulumi's technical advantages for developers.
Core Terraform Concepts
Providers
Providers are plugins that allow Terraform to interact with APIs. The AWS provider lets you manage EC2, S3, VPC. The Azure provider manages VMs, storage accounts, VNets. You declare which providers you need in your configuration and Terraform downloads them automatically.
Resources
Resources are the fundamental unit in Terraform — each resource block describes one infrastructure object. An aws_instance creates an EC2 instance; an azurerm_virtual_machine creates an Azure VM. Resources declare desired state; Terraform figures out how to achieve it.
State
Terraform maintains a state file (terraform.tfstate) that maps your configuration to the real-world resources. State is how Terraform knows what exists, what needs to be created, and what needs to be updated or destroyed. In teams, state is stored remotely (S3 bucket, Azure Blob, Terraform Cloud) with state locking to prevent concurrent modifications.
Modules
Modules are reusable Terraform configurations. A vpc module might encapsulate all the resources needed to create a production-grade VPC (subnets, route tables, NAT gateways, security groups) so teams can create consistent VPCs across all environments with a single module call.
Workspaces
Workspaces let you manage multiple environments (dev/staging/prod) from the same Terraform configuration with separate state files. Combined with variable files, they enable environment-specific deployments without code duplication.
Terraform Learning Roadmap: 6-Stage Path
This roadmap is used in Thick Brain Technology's Terraform & IaC for DevOps training program — 60 hours of live training, real cloud labs, and HashiCorp certification preparation.
HCL & Fundamentals
HCL syntax, variables, locals, data sources, output values.
BeginnerCore Workflow
Init, Plan, Apply, Destroy, Terraform CLI commands.
BeginnerState Management
Remote state, state locking, terraform import, state commands.
IntermediateModules & Reusability
Module creation, module sources, Terraform Registry, versioning.
IntermediateCloud Providers (AWS/Azure/GCP)
Provisioning compute, networking, storage, and Kubernetes clusters.
AdvancedCI/CD & Production IaC
GitOps, Atlantis, Terraform Cloud, policy enforcement.
AdvancedTop Terraform Certifications 2026
These are the certifications that appear most frequently in senior Terraform/DevOps job descriptions across Bengaluru, Hyderabad and Pune.
Terraform / IaC Engineer Salary Guide 2026
Salary data based on Bangalore market rates, job postings, and Thick Brain placement data (2025–2026).
| Role | Experience | Bangalore Salary (2026) |
|---|---|---|
| DevOps Engineer (Terraform) | 1-3 years | ₹8 – 14 LPA |
| Senior DevOps / IaC Engineer | 3-6 years | ₹14 – 22 LPA |
| Cloud Infrastructure Engineer | 5-8 years | ₹20 – 32 LPA |
| Platform / SRE Engineer | 6-10 years | ₹22 – 38 LPA |
| Cloud Architect (Multi-Cloud IaC) | 8+ years | ₹35 – 55 LPA |
Source: Naukri.com, LinkedIn Jobs, Thick Brain placement data, June 2026
🚀 Ready to master Terraform?
Book a free 60-minute demo class — write your first Terraform configuration live. No payment, no commitment.
100 Terraform Interview Questions & Answers (2026)
The most comprehensive Terraform interview question bank for Bangalore tech companies — covering HCL, state management, modules, providers (AWS/Azure/GCP), CI/CD, and certification prep. Use search and category filters to focus your preparation.
terraform plan to see a dry run of changes), Apply (run terraform apply to execute the plan and provision resources). This preview-before-apply approach makes infrastructure changes safe, auditable, and repeatable.aws_instance, aws_s3_bucket). Data source blocks fetch read-only information from external sources (e.g., aws_ami, aws_subnet) to use in your configuration. Data sources do not create or modify resources — they only provide data that you can reference elsewhere.variables.tf or inline. Example: variable "instance_type" { description = "EC2 instance type" type = string default = "t3.micro" }. Access via var.instance_type. Variables can be assigned via default values, terraform.tfvars, environment variables (TF_VAR_*), or CLI flags (-var).locals { resource_name = "my-${var.environment}-app" }. Access via local.resource_name.output "instance_id" { value = aws_instance.web.id }. Use outputs to share resource IDs, IPs, DNS names, or any other computed value. Outputs are also shown after terraform apply and can be referenced by other Terraform configurations via terraform_remote_state.terraform init initialises a Terraform working directory. It: (1) Downloads and installs the required provider plugins. (2) Initialises the backend configuration (remote state). (3) Downloads module dependencies. (4) Sets up the working directory for use. You must run terraform init before any other Terraform commands in a new directory.terraform plan creates an execution plan — it shows what changes Terraform will make (add, modify, delete) without actually applying them. terraform apply executes the plan and applies the changes to real infrastructure. Always review the plan before applying, especially in production environments.terraform destroy is used to delete all infrastructure resources managed by a Terraform configuration. It is the opposite of terraform apply. Use with extreme caution in production — it can delete everything. Always run terraform plan -destroy to preview what will be destroyed before executing terraform destroy.terraform.tfstate) is a JSON file that maps your configuration to real-world resources. It tracks resource IDs, metadata, and dependencies. Without state, Terraform cannot know what it previously created. State is critical for: (1) Detecting drift (manually made changes). (2) Dependency management. (3) Resource updates and deletion. Never commit state files to Git — use remote state instead.backend block. Example: terraform { backend "s3" { bucket = "my-tf-state" key = "prod/terraform.tfstate" region = "ap-south-1" } }. Remote state enables team collaboration, state locking, and prevents state loss. It is mandatory for production Terraform usage.terraform apply simultaneously could corrupt the state. AWS S3 + DynamoDB enables locking — DynamoDB stores a lock ID. When a user runs apply, a lock is acquired; other users get an error. The lock is released automatically after completion. State locking is essential for team collaboration.terraform import brings existing infrastructure (created manually or outside Terraform) under Terraform management. Example: terraform import aws_instance.web i-0a1b2c3d4e5f. After import, the resource appears in state but you still need to write the matching Terraform config manually. Use cases: migrating legacy resources to IaC, or recovering when state was lost.terraform state mv. Example: terraform state mv aws_instance.web module.new_web.aws_instance.web. This moves the resource from one state location to another without destroying it. Useful when refactoring configurations or moving resources between modules. You can also use terraform state mv to rename a resource.sensitive = true in output variables to prevent displaying secrets. (4) Enable access logging and restrict access to the state file via IAM policies. (5) Use Vault or external secrets for sensitive values instead of hardcoding them.terraform state command family provides subcommands to inspect and modify state manually: terraform state list (list all resources in state), terraform state show (show details of a specific resource), terraform state mv (move resource to a different location in state), terraform state rm (remove a resource from state without destroying it). Use these commands cautiously — they bypass the usual workflow.terraform plan — it shows the diff between state and real resources. To fix drift: (1) Update the Terraform code to match the real state. (2) Use terraform import to bring manual changes into state. (3) Redeploy to revert to code if manual changes are undesirable. Prevent drift by always using Terraform for changes and enforcing CI/CD.terraform destroy physically destroys the real infrastructure resource (e.g., deletes an EC2 instance) and removes it from state. terraform state rm only removes the resource from state — it does NOT delete the real resource. The resource still exists but Terraform no longer manages it. Use state rm when you want to stop managing a resource without deleting it.backend "s3" { bucket = "tf-state-bucket" key = "env/prod/terraform.tfstate" region = "ap-south-1" dynamodb_table = "tf-state-lock" encrypt = true }. The DynamoDB table must have a primary key of LockID (String). When one engineer runs terraform apply, a lock record is written to DynamoDB — any concurrent apply fails immediately. The lock is released automatically after apply completes. This prevents state corruption from race conditions..tf files with defined inputs (variables.tf) and outputs (outputs.tf). Use modules to: (1) Encapsulate and reuse infrastructure patterns. (2) Reduce code duplication. (3) Enforce consistency across environments. Example: a vpc module can be reused across development, staging, and production environments.modules/ec2) with: main.tf (resource definitions), variables.tf (input parameters), outputs.tf (return values). Call it from the root module: module "web" { source = "./modules/ec2" instance_type = "t3.micro" }. Modules enforce consistency — change the security group rule in one place and all EC2 instances using that module get the update.registry.terraform.io) is a repository of pre-built, community-validated modules. Use them by specifying the source: module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.0.0" }. The registry supports versioning, documentation, and provider constraints. Using registry modules accelerates development and follows industry best practices.source = "terraform-aws-modules/vpc/aws" version = "5.0.0". Always specify a version for production modules.webserver module might call a vpc module, an ec2 module, and an rds module. Use nesting to build complex, reusable infrastructure patterns. Keep nesting depth reasonable (2-3 levels) to avoid complexity.vpc_id to the ec2 module; the ec2 module returns instance_id as an output, which the parent can then use or pass to another module.count is a meta-argument that creates multiple instances of a resource or module. Example: resource "aws_instance" "web" { count = 3 ... } creates 3 EC2 instances. Access each instance via aws_instance.web[0]. Use count for simple, static lists of resources. For more complex conditionals, use for_each.for_each creates resources based on a map or set of strings, allowing resource-specific keys. Example: for_each = { "web" = "t3.micro", "db" = "t3.medium" }. Unlike count (which uses integer indices), for_each uses string keys, making resources easier to reference and modify. Use for_each when you need to create resources with distinct configurations.depends_on explicitly defines a dependency between resources when Terraform's automatic dependency detection fails. Example: resource "aws_instance" "web" { depends_on = [aws_iam_role_policy.web] }. Use it sparingly — Terraform's implicit dependency graph is usually correct. Use depends_on only for hidden dependencies (e.g., when a resource uses a value that isn't directly referenced).count — set count = var.create ? 1 : 0 to conditionally create a resource. (2) for_each — use a conditional map to create resources based on conditions. (3) condition in variables — validate input values. (4) Ternary operator — var.env == "prod" ? "large" : "small" for dynamic values. (5) locals with conditional functions (merge, lookup) to build dynamic structures.resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" vpc_security_group_ids = [aws_security_group.web.id] subnet_id = aws_subnet.public.id }. Use a data source to get the latest AMI: data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"] }. For production, use user_data for post-provisioning configuration.resource "aws_s3_bucket" "my_bucket" { bucket = "my-unique-bucket-name" }. Enforce encryption: resource "aws_s3_bucket_server_side_encryption_configuration" "encrypt" { bucket = aws_s3_bucket.my_bucket.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } }. Also set a bucket policy to block public access: resource "aws_s3_bucket_public_access_block" "block" { bucket = aws_s3_bucket.my_bucket.id block_public_acls = true }.terraform-aws-modules/eks/aws module. Example: module "eks" { source = "terraform-aws-modules/eks/aws" version = "19.0.0" cluster_name = "my-cluster" cluster_version = "1.28" vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets }. The module handles control plane, node groups, IAM, and security groups. For production, configure node_groups with scaling policies and add-ons like VPC CNI and CoreDNS.provider "aws" { region = "us-east-1" alias = "east" } provider "aws" { region = "ap-south-1" alias = "south" }. Then specify the alias when creating resources: resource "aws_instance" "web_east" { provider = aws.east ... }. This allows deploying resources across multiple regions from a single Terraform configuration.resource "aws_iam_role" "ec2_role" { name = "ec2-exec-role" assume_role_policy = jsonencode({ Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } }] }) }. Attach a policy: resource "aws_iam_role_policy_attachment" "s3_read" { role = aws_iam_role.ec2_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" }. Use aws_iam_policy for custom policies with JSON documents.resource "aws_cloudwatch_log_group" "vpc_flow_logs" { name = "vpc-flow-logs" } resource "aws_flow_log" "vpc" { iam_role_arn = aws_iam_role.flow_logs.arn log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn log_destination_type = "cloud-watch-logs" resource_id = aws_vpc.main.id traffic_type = "ALL" }. Flow logs are essential for security auditing and network troubleshooting.aws_availability_zones data source returns a list of availability zones in the current region. Example: data "aws_availability_zones" "available" { state = "available" }. Use it to create resources across multiple AZs dynamically: subnet_ids = data.aws_availability_zones.available.names. This makes configurations region-agnostic and portable.resource "aws_lb" "app" { name = "app-lb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.lb.id] subnets = aws_subnet.public[*].id }. Add listeners, target groups, and rules. For ALB, define a listener: resource "aws_lb_listener" "app" { load_balancer_arn = aws_lb.app.arn port = 80 protocol = "HTTP" default_action { type = "forward" target_group_arn = aws_lb_target_group.app.arn } }.aws_lb_target_group defines a group of targets (EC2 instances, IPs, Lambda functions) to route traffic to. It includes health check settings, stickiness, and protocol. aws_lb_listener_rule defines routing rules on a listener — path-based routing, host-based routing, and prioritisation. Example: listener_rule { condition { path { values = ["/api/*"] } } action { type = "forward" target_group_arn = aws_lb_target_group.api.arn } }.resource "aws_secretsmanager_secret" "db_password" { name = "prod/db/password" } resource "aws_secretsmanager_secret_version" "db_password" { secret_id = aws_secretsmanager_secret.db_password.id secret_string = "my-secure-password" }. Then reference it in other resources using a data source: data "aws_secretsmanager_secret_version" "db_password" { secret_id = aws_secretsmanager_secret.db_password.id }. Never hardcode secrets in .tfvars or state files.resource "azurerm_virtual_machine" "web" { name = "web-vm" location = "eastus" resource_group_name = azurerm_resource_group.rg.name network_interface_ids = [azurerm_network_interface.web.id] vm_size = "Standard_B1s" storage_image_reference { publisher = "Canonical" offer = "UbuntuServer" sku = "18.04-LTS" version = "latest" } storage_os_disk { name = "osdisk" caching = "ReadWrite" create_option = "FromImage" } os_profile { computer_name = "webvm" admin_username = "adminuser" admin_password = "Password1234!" } }. Use ssh_public_key instead of password for production.resource "azurerm_kubernetes_cluster" "aks" { name = "aks-cluster" location = "eastus" resource_group_name = azurerm_resource_group.rg.name kubernetes_version = "1.28" default_node_pool { name = "default" node_count = 3 vm_size = "Standard_DS2_v2" } identity { type = "SystemAssigned" } }. Use the azurerm_kubernetes_cluster_node_pool resource for additional node pools with different configurations.hashicorp/azurerm) is the official provider for Azure resources. Authenticate using: (1) Service Principal (client_id, client_secret, tenant_id, subscription_id). (2) Azure CLI (run az login first). (3) Managed Identity (when running on Azure). Best practice: use a Service Principal with least privilege and store credentials securely (e.g., environment variables or vault).resource "azurerm_key_vault" "kv" { name = "my-key-vault" location = "eastus" resource_group_name = azurerm_resource_group.rg.name tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "standard" }. Add secrets: resource "azurerm_key_vault_secret" "db_password" { name = "db-password" value = "my-secure-password" key_vault_id = azurerm_key_vault.kv.id }. Grant access via an access policy: resource "azurerm_key_vault_access_policy" "policy" { key_vault_id = azurerm_key_vault.kv.id tenant_id = data.azurerm_client_config.current.tenant_id object_id = azurerm_user_assigned_identity.app.principal_id secret_permissions = ["Get", "List"] }.resource "azurerm_virtual_network" "vnet" { name = "my-vnet" location = "eastus" resource_group_name = azurerm_resource_group.rg.name address_space = ["10.0.0.0/16"] }. Add subnets: resource "azurerm_subnet" "public" { name = "public" resource_group_name = azurerm_resource_group.rg.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = ["10.0.1.0/24"] }. Create a subnet for each tier (public, private, database).resource "azurerm_lb" "app" { name = "app-lb" location = "eastus" resource_group_name = azurerm_resource_group.rg.name sku = "Standard" frontend_ip_configuration { name = "frontend" public_ip_address_id = azurerm_public_ip.lb.id } }. Create a backend address pool: resource "azurerm_lb_backend_address_pool" "app" { loadbalancer_id = azurerm_lb.app.id name = "backend" }. Attach the VM to the backend pool via its network interface.azurerm_key_vault_secret data source: data "azurerm_key_vault_secret" "db_password" { name = "db-password" key_vault_id = azurerm_key_vault.kv.id }. Reference it: value = data.azurerm_key_vault_secret.db_password.value. Note: the secret value will be part of the state file — ensure state encryption and access controls are in place.azurerm_resource_group creates an Azure Resource Group — a logical container for resources. Example: resource "azurerm_resource_group" "rg" { name = "prod-rg" location = "eastus" }. Almost all Azure resources in Terraform require a resource_group_name parameter referencing the resource group. Use a single resource group per environment for simpler management.resource "azurerm_storage_account" "storage" { name = "mystorageaccount" location = "eastus" resource_group_name = azurerm_resource_group.rg.name account_tier = "Standard" account_replication_type = "LRS" min_tls_version = "TLS1_2" }. For production, use account_replication_type = "GRS" for geo-redundancy. Add storage containers and files as needed.resource "azurerm_monitor_diagnostic_setting" "example" { name = "example-logging" target_resource_id = azurerm_storage_account.storage.id storage_account_id = azurerm_storage_account.logs.id log { category = "StorageRead" enabled = true retention_policy { enabled = true days = 30 } } }. Use Terraform to configure logs, metrics, and alerts. Azure Monitor integrates with Log Analytics for advanced analytics.resource "google_compute_instance" "web" { name = "web-vm" machine_type = "e2-micro" zone = "us-central1-a" boot_disk { initialize_params { image = "ubuntu-os-cloud/ubuntu-2004-lts" } } network_interface { network = "default" access_config { } } }. Use a data source for the latest image: data "google_compute_image" "ubuntu" { family = "ubuntu-2004-lts" project = "ubuntu-os-cloud" }.resource "google_container_cluster" "gke" { name = "gke-cluster" location = "us-central1" node_version = "1.28" initial_node_count = 3 node_config { machine_type = "e2-standard-2" oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"] } remove_default_node_pool = true } resource "google_container_node_pool" "nodes" { cluster = google_container_cluster.gke.name node_count = 3 node_config { machine_type = "e2-standard-2" } }. Use the google_container_cluster resource for control plane and google_container_node_pool for worker nodes.hashicorp/google) is the official provider for GCP resources. Authenticate using: (1) Service Account (JSON key file) set via GOOGLE_APPLICATION_CREDENTIALS environment variable. (2) Application Default Credentials (ADC) using gcloud auth application-default login. (3) GCE Metadata service when running on GCP. Best practice: use a service account with least privilege and store the key securely.resource "google_secret_manager_secret" "db_password" { secret_id = "db-password" replication { auto {} } } resource "google_secret_manager_secret_version" "db_password" { secret = google_secret_manager_secret.db_password.id secret_data = "my-secure-password" }. Access the secret using a data source: data "google_secret_manager_secret_version" "db_password" { secret = google_secret_manager_secret.db_password.id }.resource "google_compute_network" "vpc" { name = "my-vpc" auto_create_subnetworks = false }. Add subnets: resource "google_compute_subnetwork" "public" { name = "public" network = google_compute_network.vpc.id region = "us-central1" ip_cidr_range = "10.0.1.0/24" }. For private subnets, use private_ip_google_access = true to enable access to Google APIs.terraform init, terraform plan, and terraform apply automatically on PR or main branch. (2) Store state remotely (S3, Terraform Cloud) for team access. (3) Use -auto-approve only for production after manual approval step. (4) Plan and apply in separate stages — plan on PR, apply on merge to main. Example GitHub Actions workflow: terraform init && terraform plan -out plan.out (PR), then terraform apply plan.out (merge).terraform plan. When a PR is merged, it runs terraform apply. Atlantis comments on PRs with the plan output and allows approvers to approve and apply via comment (atlantis approve). Benefits: (1) Full audit trail. (2) No manual Terraform commands. (3) Standardised workflows. Thick Brain's Terraform course includes setting up Atlantis.environments/dev/, environments/prod/ each with their own terraform.tfvars and backend config. Use modules/ for reusable components. Use terraform workspace as an alternative for small projects. In CI/CD, use terraform plan -var-file="environments/dev/terraform.tfvars" for environment-specific deployment.name: Terraform on: push: branches: [main] jobs: terraform: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.6.0 - name: Init run: terraform init - name: Plan run: terraform plan -out plan.out - name: Apply run: terraform apply plan.out. For production, add manual approval using environment or review steps.dynamic "ingress" { for_each = var.ingress_rules content { from_port = ingress.value.from to_port = ingress.value.to protocol = ingress.value.protocol } }. Use dynamic blocks when the number or content of nested blocks varies based on input. They reduce code duplication and make configurations more flexible.create_before_destroy (create new resource before destroying old), prevent_destroy (prevent accidental deletion, require terraform destroy with -force), ignore_changes (ignore certain attributes after creation). Example: lifecycle { create_before_destroy = true }. Use prevent_destroy for critical resources like databases.terraform fmt reformats Terraform configuration files to a standard style (consistent indentation, spacing, and block layout). It is the equivalent of a code linter. Run it as part of your CI/CD pipeline to enforce consistency across the team. Example: terraform fmt -recursive -check (validate without rewriting).terraform validate checks a Terraform configuration for syntax errors, invalid resource references, and provider compatibility. It does not access any remote resources or state. Run it after terraform fmt and before terraform plan in CI/CD pipelines to catch errors early.terraform refresh updates the state file to match real-world infrastructure without making changes. It is useful for detecting drift or manually correcting state after external changes. In modern Terraform, terraform plan automatically refreshes state — so explicit terraform refresh is rarely needed.required_providers block: terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } }. Use ~> (compatible with minor versions), !=, >=, etc. Pinning versions prevents unexpected changes from provider updates. Run terraform init -upgrade to upgrade to the latest allowed version.environments/dev/) use separate folders with different config and state backends. Recommended for production use because it makes environment drift visible and allows different configurations per environment.provisioner "remote-exec" { inline = ["sudo apt update", "sudo apt install nginx -y"] }. Use provisioners sparingly — they can cause state drift and are not idempotent. For configuration management, prefer user_data (AWS), cloud-init, or Ansible over provisioners.terraform taint marks a resource for forced recreation on the next apply, even if no config change is detected. Deprecated in Terraform 0.15.2+. terraform apply -replace="aws_instance.web" is the modern replacement — it forces recreation and shows a plan before replacement. Use -replace for manual resource replacement (e.g., certificate rotation, AMI upgrade).local) is for individual use only. Remote state (s3, azurerm, gcs, terraform cloud) is for teams. The backend block is the first thing you configure in a Terraform project and cannot be changed without migrating state.variable "security_groups" { type = list(string) }. In the module call: security_groups = ["sg-123", "sg-456"]. For complex data, use type = any or type = object({...}). Use maps for key-value pairs (e.g., type = map(string)).terraform state list to inspect and terraform state rm to remove problematic resources, then re-import. (3) Manual resolution — edit the state JSON directly (use terraform state push to re-upload). (4) Terraform Cloud — provides automated backups and version history. Prevention: use remote state with locking, enable versioning, and never edit state manually.terraform graph generates a visual representation of the dependency graph of a Terraform configuration. It outputs DOT format, which can be converted to an image using Graphviz. Use it to understand complex module dependencies and to identify circular dependencies or unnecessary connections.import "tfplan" main = rule { all tfplan.resources.aws_instance as instance { instance.applied.lifecycle = "create" } }. Sentinel policies run before plan or apply and can enforce restrictions (e.g., block public S3 buckets, require specific tags). For OSS Terraform, use OPA Gatekeeper with Terraform.terraform apply prompts for confirmation before applying changes. terraform apply -auto-approve skips the confirmation prompt. Use -auto-approve only in CI/CD pipelines with proper controls (e.g., after a plan is approved via PR review). Never use -auto-approve in production without a manual approval step.terraform providers lists all provider requirements and versions in the current configuration. It also shows the provider versions used in the current lock file. Useful for auditing provider versions and checking for compatibility issues.data "terraform_remote_state" "vpc" { backend = "s3" config = { bucket = "tf-state-bucket" key = "vpc/terraform.tfstate" region = "us-east-1" } }. This allows sharing outputs across configurations.terraform console starts an interactive shell for evaluating Terraform expressions. Use it for: (1) Testing interpolation syntax (var.instance_type, local.resource_name). (2) Debugging complex function calls (join(",", var.list)). (3) Inspecting data source outputs. It is a powerful tool for learning and debugging, not for production use.git tag v1.0.0. For modules in the Terraform Registry, use semantic versioning in the versions.tf file. For modules from Git sources: source = "git::https://github.com/org/terraform-module.git?ref=v1.0.0". Versioning is critical for stability — always pin a version in production code..terraform.lock.hcl file records the exact version hashes of every provider used in the configuration. It ensures that all team members and CI/CD pipelines use the same provider versions, even when new versions are released. Commit the lock file to Git. To upgrade providers, run terraform init -upgrade.terraform apply). Solutions: (1) Wait for the other process to complete. (2) Force unlock if the process is stuck: terraform force-unlock LOCK_ID. (3) Identify the process using the Lock ID (which is written in the error message). Use force-unlock with caution — it can cause state corruption if the other process is still running.AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, ARM_CLIENT_ID, ARM_CLIENT_SECRET. (2) Shared configuration files — ~/.aws/credentials, ~/.azure/credentials. (3) Identity federation — AWS IRSA, Azure Managed Identity, GCP Workload Identity. (4) Vault — retrieve credentials from HashiCorp Vault.terraform workspace manages workspaces: terraform workspace new dev (create), terraform workspace list (list), terraform workspace select dev (switch). Workspaces allow managing multiple states from the same configuration. Use terraform.workspace to get the current workspace name in your configuration.resource "aws_lambda_function" "app" { filename = "app.zip" function_name = "my-app" role = aws_iam_role.lambda.arn handler = "index.handler" runtime = "nodejs18.x" source_code_hash = filebase64sha256("app.zip") }. Use archive_file data source to package code. Integrate with API Gateway or EventBridge for event-driven execution.aws_lb_target_group defines a target group for load balancing. Example with health check: resource "aws_lb_target_group" "web" { name = "web-tg" port = 80 protocol = "HTTP" vpc_id = aws_vpc.main.id health_check { enabled = true path = "/" healthy_threshold = 2 unhealthy_threshold = 2 timeout = 5 interval = 30 } }. Health checks are critical for routing traffic only to healthy instances.terraform fmt and terraform validate in CI on every PR. (2) Run terraform plan in CI and post the output as a comment on the PR. (3) Require approve for terraform apply for production changes. (4) Use Atlantis to automate the whole process. (5) Code review the plan — review the intended changes, not just the code.vault provider. Vault manages secret rotation and provides short-lived credentials. Terraform retrieves dynamic secrets at apply time. Example: data "vault_aws_access_credentials" "creds" { backend = "aws" role = "my-role" }. Use Vault's dynamic credentials feature — secrets have a short TTL and are automatically rotated. For AWS, use aws_iam_access_key with rotation via lifecycle rules.terraform apply -target=aws_instance.web applies changes only to the specified resource and its dependencies. terraform destroy -target=aws_instance.web destroys only the specified resource. Use with caution — targeting bypasses the full dependency graph and can lead to inconsistencies. Prefer full apply or destroy for production changes.count and depends_on — use for_each and implicit dependencies. (3) Ignoring remote state — always use remote state in team settings. (4) No module design — senior engineers should demonstrate module reusability. (5) Lack of CI/CD integration — discuss how Terraform fits into a pipeline. (6) Not handling secrets — mention Vault or AWS Secrets Manager.Frequently Asked Questions
Conclusion: Master Terraform in 2026
Terraform has won the Infrastructure as Code wars. In 2026, writing Terraform for multi-cloud environments is a core competency for any DevOps engineer or cloud architect. Engineers who combine Terraform with Kubernetes, CI/CD pipelines, and cloud platforms (AWS/Azure/GCP) are among the most sought-after professionals in Bangalore's tech market.
The demand for Terraform engineers is stronger than ever. The ability to write production-quality Terraform code is a non-negotiable skill for every DevOps engineer and cloud architect. Certifications like the HashiCorp Terraform Associate validate these skills and command a significant salary premium.
Thick Brain Technology's Terraform & IaC for DevOps course teaches production-grade Terraform from first principles — covering AWS and Azure providers, module design, remote state, GitOps workflows, and CI/CD integration. Book a free demo class to write your first Terraform configuration live.
Master Terraform with Real Cloud Labs
Book a free demo class and write your first Terraform configuration live. No payment required.
Share this article
