What Is Terraform?
Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp that allows engineers to define, provision, and manage infrastructure using configuration files instead of manually creating resources through cloud consoles. With Terraform, you can describe AWS networks, servers, databases, IAM roles, DNS records, and other infrastructure components in code, then apply that configuration consistently across environments.
Terraform is particularly useful when infrastructure becomes too complex to manage manually. It supports reusable modules, dependency management, execution plans, and state tracking, making infrastructure changes easier to review, automate, and reproduce. However, Terraform is not a deployment orchestrator or configuration-management tool, and using it safely requires understanding state, drift, resource replacement, and concurrency.
Table of Contents
- Why Terraform Exists
- How Terraform Works
- Terraform State and Infrastructure Changes
- Modules, Variables, and Environments
- Production Design Example
- Ready-to-Use Example: Deploy an AWS S3 Bucket
- Security, Reliability, and Production Best Practices
- Frequently Asked Questions
- Conclusion
Why Terraform Exists
Imagine an application that requires a VPC, three private subnets, an Application Load Balancer, multiple compute instances, a PostgreSQL database, and several IAM roles. Creating everything manually through the AWS Console may be manageable once, but reproducing the same infrastructure for staging, production, or disaster recovery becomes increasingly difficult.
Terraform solves this problem by representing infrastructure as version-controlled configuration. Instead of recording which buttons someone clicked, the repository describes the resources that should exist and how they relate to one another.
Problems with Manual Infrastructure Management
Manual provisioning introduces several recurring operational problems:
- Configuration drift: Staging and production gradually develop different settings.
- Human error: Engineers accidentally select the wrong subnet, instance type, or security group.
- Limited reproducibility: Recreating an environment requires remembering previous decisions.
- Poor auditability: Infrastructure changes may happen without code reviews or documented approvals.
- Slow disaster recovery: Rebuilding infrastructure becomes a manual process during an incident.
- Unclear dependencies: Engineers must determine the correct order for creating interconnected resources.
Infrastructure as Code addresses these problems by treating infrastructure definitions as software artifacts that can be reviewed, tested, versioned, and deployed through automated workflows.
For the underlying concept, read What Is Infrastructure as Code?.
Terraform vs. CloudFormation, Ansible, and Pulumi
Terraform is not the only infrastructure automation tool. The alternatives differ in configuration language, execution model, supported platforms, and operational responsibilities.
| Tool | Primary Purpose | Typical Approach |
|---|---|---|
| Terraform | Provision and manage infrastructure | Declarative HCL configuration with providers and state |
| AWS CloudFormation | Provision AWS infrastructure | AWS-native declarative templates and stacks |
| Ansible | Configure systems and automate operations | Task-oriented playbooks and modules |
| Pulumi | Define infrastructure using programming languages | Languages such as Python and TypeScript with managed state |
Terraform is attractive when teams need a consistent provisioning workflow across AWS, Azure, Google Cloud, Kubernetes, SaaS services, and other supported APIs. CloudFormation can be a natural choice for AWS-only organizations, while Ansible is often used for configuring operating systems or software after infrastructure exists.
These tools can also complement one another. For example, Terraform can provision virtual machines, while an image-building or configuration-management process prepares the software running on them.
How Terraform Works
Terraform reads configuration files, constructs a dependency graph, compares the desired infrastructure against its recorded state and provider observations, and generates an execution plan. When the plan is applied, Terraform calls provider APIs to create, update, or delete resources.
The central idea is declarative configuration: describe the desired result rather than writing every individual API operation required to produce it.
Declarative Configuration and HCL
Terraform commonly uses HashiCorp Configuration Language (HCL), typically stored in .tf files. HCL supports resource blocks, expressions, variables, functions, collections, and references between resources.
For example, the following configuration describes an AWS S3 bucket:
terraform {
# Specify a supported Terraform version.
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
# Deploy resources in the selected AWS region.
region = "us-west-2"
}
resource "aws_s3_bucket" "example" {
# Bucket names must be globally unique
# within the applicable S3 namespace.
bucket_prefix = "sysdespro-demo-"
tags = {
Environment = "development"
ManagedBy = "terraform"
}
}
The resource block identifies the provider resource type, a local Terraform name, and its configuration. Terraform determines the required AWS API operations during planning and applying.
The bucket_prefix argument lets AWS provider logic generate a unique bucket name. This is useful for examples where a fixed bucket name might already be taken.
Providers, Resources, and Data Sources
Terraform interacts with infrastructure through providers. A provider is a plugin that implements resource and data-source operations for a particular API or platform.
- Provider: Connects Terraform to a service such as AWS, Azure, or Cloudflare.
- Resource: Describes an object Terraform should manage, such as an S3 bucket or security group.
- Data source: Reads information about an existing object without taking ownership of its lifecycle.
- Output: Exposes useful values from the configuration, such as resource IDs or endpoints.
Resources can reference attributes of other resources. Terraform uses these references to determine dependencies and build an execution graph.
For example, a subnet referencing a VPC ID automatically depends on the VPC. Terraform normally creates the VPC first without requiring a manually specified execution order.
The Terraform Workflow
A typical Terraform workflow consists of initialization, validation, planning, applying, and eventually destroying resources when they are no longer required.
# Download required providers and initialize
# the configured backend.
terraform init
# Format Terraform configuration files.
terraform fmt -recursive
# Check configuration syntax and consistency.
terraform validate
# Preview proposed infrastructure changes.
terraform plan
# Apply the reviewed configuration.
terraform apply
# Show resources tracked by Terraform.
terraform state list
# Preview resource deletion.
terraform plan -destroy
terraform plan is especially important because it reveals proposed additions, in-place updates, and replacements before infrastructure changes occur.
However, a plan is a point-in-time proposal, not a permanent guarantee. External infrastructure changes, provider behavior, or differences between planning and applying can affect the final operation.
Terraform State and Infrastructure Changes
Terraform maintains state to track the infrastructure objects associated with its configuration. State is central to how Terraform manages resource identities, dependencies, and changes across repeated runs.
Mismanaging Terraform state is one of the most common causes of infrastructure automation incidents, particularly when multiple engineers or pipelines operate on the same environment.
Why Terraform Needs State
Suppose a configuration defines an AWS EC2 instance named aws_instance.api. Terraform needs to know which real EC2 instance corresponds to that resource address.
State stores this association and other information required to manage the resource lifecycle.
Without a consistent state record, Terraform may be unable to reliably determine which existing infrastructure objects belong to its configuration.
State also contains resource attributes that may be sensitive. Even when an output is marked sensitive, the underlying value may still exist in state, depending on how the provider and resource represent it.
Remote State, Locking, and Encryption
Local state is convenient for experimentation, but shared production infrastructure should generally use a remote backend with access controls, encryption, backups, and locking support.
For AWS environments, Terraform supports an S3 backend with native lockfile-based locking in supported versions:
terraform {
backend "s3" {
# Create this bucket before initializing
# the Terraform project.
bucket = "my-company-terraform-state"
# Keep separate state for each environment.
key = "production/network/terraform.tfstate"
region = "us-west-2"
# Enable native S3 lockfile-based locking.
use_lockfile = true
# Request server-side encryption.
encrypt = true
}
}
The S3 bucket must already exist. Backend configuration is initialized before Terraform can manage the resources described by the main configuration, so production teams often bootstrap state infrastructure separately.
Enable S3 versioning, restrict access with IAM and bucket policies, and use an appropriate encryption configuration. The encrypt setting does not replace bucket-level security controls.
Locking prevents cooperating Terraform processes from modifying the same state concurrently. Avoid disabling locking to bypass a blocked deployment; investigate the existing operation and confirm whether a stale lock can be safely released.
Drift Detection and Resource Replacement
Infrastructure drift occurs when real infrastructure no longer matches the expected configuration, often because someone modifies resources outside Terraform.
For example, an engineer may manually change a security group rule in the AWS Console. A subsequent Terraform plan can detect the difference and propose restoring the configured value.
Not every difference results in an in-place update. Some resource attributes require replacement, meaning Terraform may need to destroy the existing resource and create a new one.
This distinction is critical for stateful infrastructure such as databases, storage volumes, and network resources. A replacement may cause downtime, data loss, or downstream dependency changes.
Modules, Variables, and Environments
Terraform modules allow engineers to group related resources behind a reusable interface. Rather than copying VPC, subnet, route table, and security group definitions into every environment, a team can maintain a common network module.
Modules improve consistency, but they also create versioning responsibilities. A breaking module change can affect many environments if consumers update without reviewing the resulting plans.
Reusable Terraform Modules
A module can expose variables and outputs, allowing callers to customize infrastructure without duplicating its internal implementation.
For example, a root configuration can call a network module:
module "network" {
# Load a reusable module from the repository.
source = "./modules/network"
# Pass environment-specific configuration.
environment = "production"
vpc_cidr = "10.20.0.0/16"
availability_zones = [
"us-west-2a",
"us-west-2b",
]
}
output "vpc_id" {
# Expose an output defined by the module.
value = module.network.vpc_id
}
The example assumes the module defines the referenced input variables and a vpc_id output. It is an illustration of module composition, not a complete standalone network deployment.
For production modules, document inputs and outputs, define validation rules, and version reusable modules. Avoid exposing every internal resource setting as a variable unless callers genuinely need that flexibility.
Environment Isolation and Workspaces
Development, staging, and production often share architectural patterns but require different resource sizes, networking rules, and operational safeguards.
Two common approaches are:
- Separate root configurations: Each environment has its own configuration entry point and state location.
- Terraform workspaces: Multiple state instances are associated with the same configuration.
For critical environments, separate AWS accounts, state locations, and CI/CD permissions often provide stronger operational boundaries than relying only on Terraform workspaces.
A practical repository might organize environments as environments/dev, environments/staging, and environments/prod, with shared modules under modules/.
Production Design Example
Consider a SaaS application running on AWS. The platform has a public API, background workers, PostgreSQL, Redis, and an object-storage layer. Terraform manages the foundational infrastructure while the application deployment pipeline manages container image releases.
The infrastructure is split into independent Terraform root configurations to reduce the blast radius of changes and avoid forcing unrelated services to share one large state file.
Production Infrastructure Architecture
The infrastructure repository separates resources into several layers:
- Networking: VPC, public and private subnets, routing, and security groups.
- Data: Amazon RDS, ElastiCache, and storage resources.
- Compute: ECS clusters, task execution roles, services, and load balancers.
- Security: IAM policies, KMS configuration, and secret access.
- Observability: Log groups, alarms, and monitoring infrastructure.
Each layer has an independently controlled state and deployment process. Dependencies between layers are handled through explicit interfaces, such as published VPC IDs, database endpoints, or configuration values.
This structure avoids placing every production resource under one Terraform plan, but it introduces coordination requirements when one infrastructure layer changes an interface consumed by another.
Terraform in a CI/CD Pipeline
Infrastructure changes are submitted through pull requests. CI validates the configuration, performs security checks, and generates a plan for review.
A production pipeline typically follows this process:
- Run
terraform fmt -checkandterraform validate. - Run static analysis and policy checks.
- Generate a plan using a restricted CI identity.
- Publish a readable plan summary for reviewers.
- Require approval for destructive or high-risk changes.
- Apply the approved plan using controlled credentials.
- Verify resource health and monitor for regressions.
For workflows that save a binary plan, the apply stage can use the exact saved plan artifact. Treat plan files as sensitive artifacts because they can contain infrastructure details and secret values.
Plan approval should account for the age of the plan and the state version it was generated against. Stale plans should be regenerated instead of blindly applied.
Failure Scenario: An Unexpected Database Replacement
Suppose a developer modifies an RDS configuration attribute that requires replacement. Terraform generates a plan showing the existing database being destroyed and a new database being created.
If the plan is approved without reviewing the replacement action, the deployment may cause prolonged downtime or permanent data loss.
A safer production process includes deletion protection where supported, backup and snapshot policies, resource lifecycle safeguards, and mandatory review of replacement operations.
For example, Terraform's prevent_destroy lifecycle setting can reject plans that would destroy a protected resource while the protection remains in its configuration. It is a useful safeguard, but not a substitute for cloud-side deletion protection, backups, or restricted permissions.
For related reliability principles, see Designing Highly Available Cloud Systems.
Ready-to-Use Example: Deploy an AWS S3 Bucket
This example creates a private S3 bucket with versioning, server-side encryption, public access blocking, and resource tags. It also demonstrates variables, outputs, planning, applying, verification, and cleanup.
You need Terraform installed, an AWS account, and AWS credentials with permissions to manage the required S3 resources. The example uses the AWS provider and may create billable resources depending on usage.
1. Create the Project Structure
Create a directory named terraform-s3-demo with the following files:
versions.tf— Terraform and provider version requirements.variables.tf— Configurable input values.main.tf— AWS resources.outputs.tf— Resource information returned after deployment.
Keep Terraform state files, local variable files containing secrets, and provider credentials out of source control. Commit the provider dependency lockfile after initialization so provider selections can be reviewed and reproduced.
2. Define Terraform Configuration
Create versions.tf:
terraform {
# Require a compatible Terraform CLI version.
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
# The region is configurable through a variable.
region = var.aws_region
}
Create variables.tf:
variable "aws_region" {
description = "AWS region for the S3 bucket."
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name used in tags."
type = string
default = "development"
}
variable "bucket_prefix" {
description = "Prefix for a unique bucket name."
type = string
default = "terraform-sysdespro-demo-"
validation {
# Keep the prefix lowercase and DNS-friendly.
condition = can(
regex("^[a-z0-9][a-z0-9-]*$", var.bucket_prefix)
)
error_message = "Use lowercase letters, digits, and hyphens."
}
}
Create main.tf:
resource "aws_s3_bucket" "app" {
# Let the provider generate a unique name.
bucket_prefix = var.bucket_prefix
tags = {
Name = "terraform-demo"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_versioning" "app" {
# Enable object versioning for recovery.
bucket = aws_s3_bucket.app.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "app" {
# Configure SSE-S3 encryption explicitly.
bucket = aws_s3_bucket.app.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "app" {
# Prevent public ACLs and public bucket policies.
bucket = aws_s3_bucket.app.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_ownership_controls" "app" {
# Disable ACL-based object ownership management.
bucket = aws_s3_bucket.app.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
This configuration uses separate Terraform resources for S3 versioning, encryption, public access controls, and ownership settings. Terraform creates the bucket before configuring the dependent resources.
Versioning helps recover overwritten or deleted objects, but it also increases storage consumption. Production buckets may need lifecycle policies, retention controls, and separate backup strategies.
Create outputs.tf:
output "bucket_name" {
description = "Generated S3 bucket name."
value = aws_s3_bucket.app.id
}
output "bucket_arn" {
description = "ARN of the created bucket."
value = aws_s3_bucket.app.arn
}
output "aws_region" {
description = "Region used for deployment."
value = var.aws_region
}
Outputs make resource information available to operators and automation. Avoid exposing sensitive values unnecessarily, even when they are marked as sensitive in Terraform.
3. Initialize and Preview Changes
Run the following commands from the project directory:
# Verify that AWS credentials are available.
aws sts get-caller-identity
# Download the AWS provider and initialize Terraform.
terraform init
# Format configuration files.
terraform fmt
# Validate the Terraform configuration.
terraform validate
# Preview the resources that will be created.
terraform plan -out=tfplan
# Display the saved execution plan.
terraform show tfplan
The plan should show an S3 bucket and its related configuration resources being created.
Review the resource actions and values before proceeding. The saved plan file can contain sensitive information, so do not commit or publish it.
4. Apply and Verify Infrastructure
Apply the saved plan and inspect the outputs:
# Apply the exact plan generated previously.
terraform apply tfplan
# Print the generated bucket name.
terraform output -raw bucket_name
# Verify the bucket through the AWS CLI.
aws s3api head-bucket \
--bucket "$(terraform output -raw bucket_name)"
# Verify bucket versioning.
aws s3api get-bucket-versioning \
--bucket "$(terraform output -raw bucket_name)"
# Inspect Terraform-managed resources.
terraform state list
The bucket should exist with versioning enabled and public access blocking configured. The AWS CLI commands verify selected properties; they do not constitute a comprehensive security audit.
For production environments, use remote state instead of relying on the local terraform.tfstate file created by this demonstration.
5. Update and Destroy Resources
To modify the infrastructure, change a configuration value, such as the environment tag, and generate a new plan:
# Preview a tag change without editing files.
terraform plan \
-var="environment=staging"
# Apply the reviewed configuration change.
terraform apply \
-var="environment=staging"
# Preview deletion of managed resources.
terraform plan -destroy \
-var="environment=staging"
# Destroy the demo infrastructure.
# Confirm the proposed deletion carefully.
terraform destroy \
-var="environment=staging"
If the S3 bucket contains objects or object versions, deletion may fail because the example does not enable forced bucket deletion. Remove unwanted objects and versions deliberately before retrying cleanup.
Do not use destructive commands against production infrastructure without appropriate approvals, backups, and an understanding of dependent resources.
Security, Reliability, and Production Best Practices
Terraform makes infrastructure changes repeatable, but it can also execute destructive operations across critical resources. Production safety depends on the quality of the configuration, access controls, state management, and deployment process.
Terraform should be treated as privileged infrastructure automation, not simply as a collection of configuration files.
Terraform Security and Secrets
Several practices are especially important for protecting Terraform-managed environments:
- Use short-lived credentials: Prefer IAM roles, workload identity, or OIDC-based authentication over long-lived access keys.
- Protect state: Encrypt state storage, restrict access, and enable appropriate versioning or backups.
- Pin providers: Constrain provider versions and commit the dependency lockfile.
- Review modules: Treat external modules as third-party dependencies.
- Scan configuration: Use policy and security analysis to detect dangerous infrastructure settings.
- Separate environments: Restrict production credentials and state access independently from development.
- Protect destructive resources: Use lifecycle safeguards, cloud-native deletion protection, and backup policies.
Terraform's sensitive designation helps suppress values from ordinary CLI output, but it does not automatically guarantee that secrets are absent from state or plan artifacts.
For more on credential handling, see Secrets Management in Cloud Applications.
Common Terraform Mistakes
Common Terraform mistakes include:
- Using local state for shared production infrastructure: Teams lose reliable locking and centralized access control.
- Applying without reviewing the plan: Unexpected replacements or deletions can reach production.
- Making manual cloud-console changes: Infrastructure drifts from its declared configuration.
- Storing secrets in Git: Credentials can remain exposed in repository history.
- Using overly broad IAM permissions: Compromised automation can affect unrelated infrastructure.
- Creating one enormous state file: Unrelated changes share the same operational failure domain.
- Using unversioned modules: External changes can unexpectedly alter infrastructure behavior.
- Ignoring resource replacement: Stateful services may experience downtime or data loss.
Production Checklist
Before using Terraform to manage critical infrastructure, verify the following:
- Remote state is encrypted, access-controlled, backed up, and locked during changes.
- Production environments use isolated credentials and appropriate account boundaries.
- Provider versions and reusable module versions are controlled.
- CI validates formatting, configuration, security policies, and execution plans.
- Destructive actions require explicit review and approval.
- Critical data resources have tested backup and recovery procedures.
- Terraform changes are coordinated with application releases when necessary.
- Drift detection and infrastructure monitoring are part of regular operations.
For broader guidance on infrastructure reliability, read Cloud Architecture Best Practices.
Frequently Asked Questions
Terraform is widely used in cloud infrastructure automation, but several aspects of its execution model can be confusing, especially when comparing it with cloud-native provisioning and application deployment tools.
What Is Terraform Used For?
Terraform is used to provision and manage infrastructure such as networks, compute instances, databases, IAM policies, DNS records, Kubernetes resources, and SaaS platform configurations. Its primary value is defining infrastructure declaratively and managing changes through repeatable plans and state tracking.
Is Terraform Free?
Terraform offers a free-to-use Community Edition under HashiCorp's Business Source License for modern releases, subject to its license terms. HashiCorp also offers commercial infrastructure-management products and services. OpenTofu is a separate open-source fork of Terraform created following Terraform's licensing change.
What Is the Difference Between Terraform and CloudFormation?
Terraform uses providers to manage infrastructure across many platforms and maintains state through a configured backend. AWS CloudFormation is an AWS-native service that manages resources through stacks and templates.
CloudFormation integrates directly with AWS resource lifecycle management, while Terraform provides a consistent configuration and workflow model across multiple providers.
What Is the Difference Between Terraform and Ansible?
Terraform primarily provisions infrastructure resources through provider APIs. Ansible commonly configures operating systems, installs software, and automates operational tasks through playbooks.
Although their capabilities overlap, Terraform is generally more appropriate for declaring cloud infrastructure, while Ansible is often useful for configuring software running on provisioned machines.
Why Does Terraform Need a State File?
Terraform state records associations between resource addresses in configuration and real infrastructure objects. It helps Terraform determine which resources it manages and how proposed changes should be applied.
State can contain sensitive information, so it requires appropriate storage, access controls, and backup policies.
Can Terraform Manage Existing Infrastructure?
Yes. Terraform can import supported existing infrastructure into its state, allowing it to manage resources that were created manually or through other tools.
Importing a resource does not automatically guarantee that the configuration accurately reflects every existing setting. Review the imported configuration and resulting plans carefully before applying changes.
Can Terraform Deploy Kubernetes Applications?
Yes. Terraform providers can manage Kubernetes resources and Helm releases. However, teams often use Terraform to provision clusters and foundational infrastructure while using Helm, GitOps controllers, or dedicated deployment pipelines for frequent application releases.
Separating infrastructure provisioning from application deployment can reduce state churn and simplify operational responsibilities.
Conclusion
Terraform is a powerful Infrastructure as Code tool that makes infrastructure provisioning more reproducible, reviewable, and automated. By combining declarative HCL configuration, providers, dependency graphs, execution plans, modules, and state management, it gives engineering teams a consistent way to manage infrastructure across cloud and service platforms.
Its greatest operational benefits appear when Terraform is integrated with version control, CI/CD pipelines, remote state, least-privilege access, and disciplined change review. Without those safeguards, automation can make infrastructure mistakes happen faster and at a larger scale.
Key Takeaway: Terraform describes and manages desired infrastructure through code, but safe infrastructure automation requires more than writing resource definitions. State protection, plan review, controlled permissions, modular design, and tested recovery procedures are essential for reliable production operations.
More Articles to Read
Continue exploring Infrastructure as Code, cloud architecture, and reliable infrastructure deployment with these related SysDesPro articles:
Comments (0)