DevOps

How to Provision Your First Cloud Infrastructure with Terraform

Tech Setup4 min read
TS

Tech Setup

Published October 13, 2026 · Editorial policy

How to Provision Your First Cloud Infrastructure with Terraform

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, the ami hash 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 ami argument in main.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 configure to generate the credentials file, or explicitly pass access_key and secret_key variables inside your provider "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 using terraform 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.