Terraform Core Concepts¶
This section covers Terraform fundamentals, from HashiCorp’s ecosystem to providers, provisioners, and Terraform Cloud.
HashiCorp Overview¶
HashiCorp is a company specializing in managed open-source tools used to support the development and deployment of large-scale, service-oriented software environments.
HashiCorp Cloud Platform (HCP) is a unified platform providing access to HashiCorp products. It is:
- Cloud-agnostic
- Well-suited for multi-cloud workloads
Terraform Overview¶
Terraform is logically divided into two major components:
- Terraform Core: handles infrastructure state, plan, and apply operations. It communicates with providers through RPC (Remote Procedure Calls).
- Terraform Plugins: implements specific services or provisioners (compiled as Go binaries).
Terraform Core is a statically compiled binary written in Go.
Configuration Language¶
Terraform uses HCL (HashiCorp Configuration Language), designed for human readability and machine parsing.
Notable Features¶
- Modular and reusable configurations
- Predictable change planning (terraform plan)
- Automatic dependency graphing
- State management and drift detection
- Multi-language provisioning via tools like AWS CDK
- Terraform Registry — 1000+ providers available
Recommended Reading: Terraform: Up and Running by Yevgeniy “Jim” Brikman
Terraform Cloud¶
Terraform Cloud is a SaaS offering that enhances collaboration and automation.
Core Capabilities¶
- Remote state storage
- Version control integration (GitHub, GitLab, Bitbucket)
- Flexible workflow orchestration
- Team collaboration in a unified web portal
- Free tier available for up to 5 users per organization
Alternatives¶
- Standard remote backends
- Atlantis (self-hosted alternative)
- Terraform Enterprise (private deployment)
The underlying software powering both Terraform Cloud and Terraform Enterprise is known as the Terraform Platform.
Resources¶
Resources in configuration files represent infrastructure objects (VMs,
databases, networks, etc.). Each resource belongs to a provider (e.g.
aws_instance, azurerm_storage_account). A resource type determines the kind
of the infrastructure object.
Some resources expose a special timeouts nested block to customize how long
certain operations are allowed to take.
Resource Behavior¶
During terraform apply, Terraform may:
- create — new resource added to config not associated with an object within the state
- destroy — resource removed from config which exists within the state
- update in-place — modify existing resource
- replace (destroy + recreate) — resouces whose arguments have changed and cannot be updated in-place due to API limitations
null_resource¶
A placeholder resource without direct provider association. Often used with provisioners or scripts that depend on other resources.
resource "aws_instance" "cluster" {
count = 3
# ...
}
resource "null_resource" "cluster" {
# Changes to any instance of the cluster required re-provisioning
triggers = { # is a map of values which should cause this set of provisioners to re-run
cluster_instance_ids = "${join(",", aws_instance.cluster.*.id)}"
}
connection {
# ...
}
provisioner "remote-exec" {
# some bootstrap script for example
}
# ...
}
terraform_data¶
Similar to null_resource, but does not require a provider (the null_resource
implicitly uses the null provider).
resource "null_resource" "main" {
triggers = {
version = var.version
}
provisioner "local-exec" {
command = "echo ${self.triggers.version}"
}
}
is similar to
resource "terraform_data" "main" {
triggers_replace = [
version = var.version
]
provisioner "local-exec" {
command = "echo ${self.triggers_replace.version}"
}
}
Terraform Lifecycle¶
A typical Terraform workflow follows this sequence:
code → init → plan → validate → apply/destroy → repeat
Change Automation¶
Change Automation refers to the systematic and predictable handling of infrastructure change requests using Terraform’s built-in mechanisms. Terraform uses:
- Execution Plans — show what will change before applying
- Resource Graphs — visualize dependencies and execution order
ChangeSets represent a collection of version-controlled commits that modify infrastructure. They help track:
- What changed
- Who made the change
- When the change occurred
This approach reduces human error by providing clear visibility into planned infrastructure changes.
Execution Plans¶
Execution plans provide a manual review step showing what resources will be added, changed, or destroyed before applying updates.
You can visualize an execution plan as a graph using GraphViz:
terraform graph | dot -Tsvg > graph.svg
This command generates a .svg diagram of the resource dependency graph.
Resource Graph¶
The Resource Graph is a dependency model that Terraform uses to:
- Generate execution plans
- Refresh state
- Optimize parallelism during apply
It ensures resources are created, updated, or destroyed in the correct order.
Terraform Provisioners¶
Provisioners allow Terraform to execute scripts or commands on machines (for example, installing software or modifying configuration files).
Best Practice: Use provisioners only as a last resort. Prefer using cloud-init, configuration management tools (Ansible, Puppet, Chef), or image builders (Packer).
local-exec¶
Executes a local command on the machine running Terraform (not the remote instance) after a resource is provisioned. Useful for post-provision tasks, such as triggering Ansible or CLI commands.
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo ${self.private_ip} >> private_ips.txt"
interpreter = ["PowerShell", "-Command"]
working_dir = "/whatever/path"
environment = {
KEY="string"
}
}
}
remote-exec¶
Executes commands on the target resource after provisioning. Best used for simple, short-lived setup commands.
resource "aws_instance" "web" {
# ...
provisioner "remote-exec" {
inline = [
"puppet apply",
"consul join ${aws_instance.web.private_ip}",
] # list of command strings
# or
scripts = [
"./setup-users.sh",
"/path/to/bootstrap"
] # or script - local script or scripts that will be copied to the remote resource and then executed in order
# you have to choose inline/script/scripts (they don't work together)
}
}
file¶
Copies files or directories from the local machine to the remote resource.
resource "aws_instance" "web" {
# ...
provisioner "file" {
# copies the file from src to dst
source = "myapp.conf"
destination = "/etc/myapp.conf"
# or
# sets the content of the destination file
content = "ami used: ${self.ami}"
destination = "/tmp/file.log"
# you have to choose between the source vs content approach
# and also
connection {
type = "ssh"
user = "root"
password = "${var.root_password}"
host = "${var.host}"
}
# or eventually
connection {
type = "winrm"
user = "Administrator"
password = "${var.admin_password}"
host = "${var.host}"
}
}
}
Terraform Providers¶
Providers are Terraform plugins that allow interaction with APIs and external services, including:
- Cloud Providers: AWS, Azure, GCP
- SaaS Providers: GitHub, Stripe, Datadog
- Other APIs: Kubernetes, PostgreSQL, etc.
Providers are required for Terraform configurations that manage external infrastructure.
Provider Tiers¶
- Official: Maintained by the service owner (e.g., AWS, Azure)
- Verified: Community or partner-maintained, but vetted by HashiCorp
- Community: Open-source providers without guaranteed maintenance
Providers are distributed separately from Terraform.
terraform init downloads and installs the providers defined in your configuration.
Additional command:
terraform providers — lists all providers and modules used.
Provider Configuration¶
You can configure multiple providers using aliases.
provider "aws" {
alias = "west"
region = "us-west-2"
}
resource "aws_instance" "foo" {
provider = aws.west
}
Terraform Registry¶
The Terraform Registry is a public repository for browsing, downloading, and publishing:
- Providers
- Modules
Terraform Cloud allows publishing private modules for your organization within the Private Registry. When creating a module, you have to connect it to a version control repository (GitHub, GitLab, etc.).