Skip to content

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.).