How to Provision Your First Cloud Infrastructure with Terraform
Published October 13, 2026 · Editorial policy

Clicking through the AWS Console to spin up a single EC2 instance takes ten minutes, but replicating that exact setup across staging and production environments by hand guarantees human error. If you are a backend developer tired of configuration drift and "it worked on my machine" infrastructure headaches, Infrastructure as Code (IaC) is your exit strategy. By the end of this guide, you will write your first declarative configuration, initialize your workspace, and provision a live cloud server using Terraform—without ever touching a web browser dashboard.
The Mental Model: Declarative vs. Imperative
When you write bash scripts or click buttons in a cloud console, you use an imperative approach: you issue a step-by-step sequence of instructions ("create a server," "attach a disk," "open port 80"). If step three fails, your system enters an unknown, partially configured state.
Terraform uses a declarative model. You write code describing what the final infrastructure should look like, and Terraform figures out how to get there.
[Your HCL Code] ---> [Terraform Core] ---> [Cloud Provider API (AWS/GCP/Azure)]
^
|
[State File (.tfstate)]
The core components of Terraform are straightforward:
- HCL (HashiCorp Configuration Language): The human-readable syntax used to write configuration files (
.tf). - Providers: Plugins that translate your HCL code into API calls for specific platforms (AWS, DigitalOcean, Kubernetes, etc.).
- State File (
terraform.tfstate): A JSON file where Terraform maps your real-world resources to your configuration. Never edit this file manually.
Environment Setup and Installation
Before writing code, you need the Terraform binary and an authentication path to a cloud provider. For this guide, we will use AWS, but the mechanics apply universally.
1. Install the CLI
If you are on macOS using Homebrew, or Linux via apt, run the following commands to install Terraform version 1.5 or higher:
# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Ubuntu/Debian
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/local/etc/apt/trusted.gpg.d/hashicorp.gpg > /dev/null
echo "deb [signed-by=/usr/local/etc/apt/trusted.gpg.d/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install terraform
Verify the installation:
terraform -version
Expected output:
Terraform v1.5.7
on darwin_amd64
2. Configure Cloud Credentials
Terraform needs permission to provision resources in your cloud account. Install the AWS CLI and configure your credentials:
aws configure
Provide your AWS Access Key ID, Secret Access Key, default region (e.g., us-east-1), and output format (json). Terraform will automatically pick up these environment variables.
Writing Your First Configuration
Create a dedicated directory for your project and open it in your editor:
mkdir terraform-intro && cd terraform-intro
touch main.tf
Open main.tf and paste the following configuration. This block sets up the AWS provider and provisions a single Ubuntu EC2 instance.
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web_server" {
ami = "ami-0c7217cdde317cfec" # Ubuntu 22.04 LTS (x86_64) in us-east-1
instance_type = "t2.micro"
tags = {
Name = "TechSetupWebServer"
Environment = "Development"
}
}
Every resource block takes two identifiers: the resource type (aws_instance) and the local name (web_server). The local name is how other parts of your code reference this specific server.
The Core Lifecycle Commands
With your configuration written, execute the four foundational commands that drive every Terraform workflow.
1. Initialize the Working Directory
Run init to download the required provider plugins (in this case, the AWS provider) specified in your main.tf.
terraform init
Expected output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)
Terraform has been successfully initialized!
2. Preview the Execution Plan
Never apply code blindly. Run plan to see exactly what API calls Terraform will make.
terraform plan
Expected output:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.web_server will be created
+ resource "aws_instance" "web_server" {
+ ami = "ami-0c7217cdde317cfec"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ id = (known after apply)
+ instance_type = "t2.micro"
+- ...
}
Plan: 1 to add, 0 to change, 0 to destroy.
3. Provision the Infrastructure
Apply the plan to create the real-world resources. The -auto-approve flag bypasses the interactive confirmation prompt.
terraform apply -auto-approve
Expected output:
aws_instance.web_server: Creating...
aws_instance.web_server: Still creating... [10s]
aws_instance.web_server: Creation about-and-ready-deed [15s]
aws_instance.web_server: Creation complete after 16s [id=i-0123456789abcdef0]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Check your AWS console—your EC2 instance is now running. Look at your local project directory; Terraform generated a terraform.tfstate file recording the instance ID and metadata.
4. Tear Down the Environment
When testing is complete, destroy the infrastructure to prevent unwanted cloud costs.
terraform destroy -auto-approve
Expected output:
aws_instance.web_server: Destroying... [id=i-0123456789abcdef0]
aws_instance.web_server: Destruction complete after 10s
Destroy complete! Resources: 1 destroyed.
Troubleshooting Common Errors
Even simple setups can fail. Here are the three most common roadblocks for beginners and how to fix them.
1. Invalid AMI ID Error
- Symptom:
Error: launching a new EC2 instance: InvalidAMIID.NotFound - Cause: Amazon Machine Image (AMI) IDs are region-specific. If you changed your provider region to
eu-west-1, theamihash from the example above will not exist there. - Fix: Look up a valid Ubuntu 22.04 AMI ID in your target AWS region via the console or AWS CLI, and update the
amiargument inmain.tf.
2. Missing Authentication Credentials
- Symptom:
Error: No valid credential sources found for AWS Provider - Cause: The AWS CLI has not been configured, or Terraform is running in an environment without access to
~/.aws/credentials. - Fix: Run
aws configureto generate the credentials file, or explicitly passaccess_keyandsecret_keyvariables inside yourprovider "aws"block (though hardcoding keys is discouraged for production environments).
3. State Locking Conflicts
- Symptom:
Error: Error acquiring the state lock - Cause: Another Terraform process is running, or a previous run was abruptly terminated, leaving a stale lock file.
- Fix: Verify no other terminal is running
terraform apply. If you are certain no process is running, you can bypass the lock manually usingterraform force-unlock <LOCK_ID>(found in the error message), though use this with extreme caution on shared teams.
Next Steps: Moving Beyond Local State
You have successfully provisioned and destroyed cloud infrastructure using declarative code. However, keeping the terraform.tfstate file on your local machine only works for solo developers.
For team environments, configure a remote backend (such as AWS S3 with DynamoDB for state locking) so multiple developers share a single source of truth without stepping on each other's changes. From here, explore input variables (variables.tf) and output values (outputs.tf) to turn your static configuration into reusable infrastructure modules.


