Write Your First Terraform Config

Write your first configuration file in Terraform — hands-on tutorial, step-by-step, with troubleshooting and what to learn next.

Focus: write your first configuration file

Sponsored

You’ve installed Terraform, you know what Infrastructure as Code means, and now you’re staring at an empty terminal wondering, “Where do I actually start?” The theory is clear, but writing your first configuration file still feels like staring at a blank page — you’re not alone. This lesson walks you through the exact mechanics of crafting a .tf file, from directory setup to terraform apply, so you can turn a simple idea into real infrastructure with confidence.

The Problem This Lesson Solves

Terraform is a powerful tool, but its power is locked behind well-formed configuration files. The #1 stumbling block for new users isn’t the CLI — it’s the file format. Without a proper .tf file, you can’t plan, you can’t apply, and you can’t manage resources. Even worse, a sloppy first config leads to confusing errors, accidental resource deletion, and a lingering fear of breaking things.

Many beginners try to write Terraform configuration by copying random snippets from the internet, only to hit syntax errors or unexpected behavior. The root cause? They skipped the foundational structure — understanding blocks, arguments, and the declarative mindset that Terraform demands. This lesson solves that by giving you a repeatable scaffold for any provider, so your first file isn’t a gamble but a blueprint.

Core Concept / Mental Model

Think of Terraform as a shopping list for your infrastructure. Instead of writing a sequence of commands (imperative), you declare what you want — the desired state — and Terraform figures out how to get there. Your configuration file is that list, written in HCL (HashiCorp Configuration Language).

A configuration file is built from blocks and arguments:

  • Block – a container with a type and label, like resource "aws_instance" "web". It groups related settings.
  • Argument – a key-value pair inside a block, like ami = "ami-1234" or instance_type = "t2.micro".

Every .tf file follows a consistent structure that Terraform parses:

Section Purpose
terraform block Configure Terraform itself (required providers, backend)
provider block Choose the cloud provider (AWS, Azure, GCP) and region
resource block Define a real infrastructure object (server, database, network)
data block Fetch existing infrastructure info (optional)
variable / output Parameterize and expose values (optional)

Mindset shift: You’re not scripting — you’re describing. Terraform reads your file, compares it to the real world, and makes changes only as needed. That’s the declarative paradigm that will save you hours of manual toil.

How It Works Step by Step

  1. Create a project directory – each Terraform deployment gets its own folder. Inside, you’ll keep your .tf files, state, and related scripts. A dedicated directory isolates environments and teams.
  2. Write the basic configuration file – name it main.tf. Start with a terraform block that pins the required providers, then add a provider block for your cloud, and finally a resource block that describes the exact infrastructure you want. This wired order ensures Terraform knows what to install before how to install it.
  3. Initialize the working directory – run terraform init in the terminal. This downloads provider plugins and sets up the backend. You’ll see a success message and a fresh .terraform folder — that’s your dependency cache.
  4. Review the execution plan – run terraform plan. Terraform analyzes your config against the real infrastructure and prints a human-readable diff. This is your safety net before any changes.
  5. Apply the configuration – run terraform apply. Terraform asks for confirmation, executes the plan, and reports the created resources. Your infrastructure is now live.
  6. Clean up when done – use terraform destroy to remove everything, keeping your cloud bill happy.

Think of this cycle — write, init, plan, apply, destroy — as your Terraform loop. You’ll repeat it constantly as you evolve your infrastructure.

Hands-on Walkthrough

Let’s write your first configuration file together. We’ll deploy an AWS EC2 instance, but the pattern applies to any provider.

Step 1: Prepare your directory

mkdir my-first-infra
cd my-first-infra

Step 2: Create main.tf with your first config

# main.tf
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 (check your region)
  instance_type = "t2.micro"

  tags = {
    Name = "first-instance"
  }
}

💡 Pro tip: The AMI ID changes per region. Use the AWS console or CLI to find the current one for your region — otherwise Terraform will fail with InvalidAMIID.NotFound.

Step 3: Initialize, plan, and apply

# 1. Initialize the working directory   erraform init

# 2. Preview what will be created   erraform plan
# Output: Plan: 1 to add, 0 to change, 0 to destroy.

# 3. Apply the configuration (type 'yes' when prompted)
terraform apply
# Output: aws_instance.web_server: Creating...
#         aws_instance.web_server: Creation complete after 45s
#         Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

You now have a running EC2 instance. Check it in the AWS console or with aws ec2 describe-instances. When you’re done, destroy it:

terraform destroy
# Output: Destroy complete! Resources: 1 destroyed.

Why this works: The resource block declares the desired end state. Terraform’s provider plugin for AWS handles the API calls, retries, and status checks — you never write a single HTTP request.

Compare Options / When to Choose What

Your first configuration file can be written in several styles. Each has trade-offs:

Approach When to use Pros Cons
Single main.tf (flat) First experiments, tiny projects Simple to read, minimal files Hard to scale, no separation
Multiple files by resource type (ec2.tf, vpc.tf) Growing projects Logical organization, easier to find resources More files, still flat
Modules (reusable packages) Production, multi-infra reuse DRY, versionable, testable Overhead, requires upfront design

For this lesson, stick with a single main.tf. It keeps the focus on syntax and flow. Once you’re comfortable, split files by resource type — that’s free organization. Move to modules when you see the same patterns repeated across projects.

Pro tip: Always start with the terraform block with required_providers. It pins versions and avoids the “inconsistent dependency lock” errors that plague beginners later.

Troubleshooting & Edge Cases

Even a simple config can hit issues. Here are the most common ones and how to fix them:

1. terraform init fails with “Error downloading provider”

Cause: Network restrictions or a wrong source address. Fix: Verify the source field (must be hashicorp/aws) and check your internet/VPN. If you’re in a restricted environment, use a local mirror or set TF_CLI_CONFIG_FILE.

2. terraform plan shows “No changes” unexpectedly

Cause: The resource already exists (e.g., you ran apply before), or your config matches the current state. Fix: That’s actually good — Terraform is telling you the real world matches your desired state. To see changes, modify an argument (like instance_type).

3. terraform apply fails with “InvalidAMIID.NotFound”

Cause: The AMI ID doesn’t exist in the specified region. Fix: Look up the correct AMI ID for your region and update the ami argument. Every region has its own AMI IDs.

4. Unauthorized operation (403) when applying

Cause: Your AWS credentials lack permissions for EC2. Fix: Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, or configure a profile with aws configure, and ensure the IAM user has AmazonEC2FullAccess (at least for testing).

5. State file out of sync (resources manually deleted)

Cause: Someone deleted the instance outside Terraform. Fix: Run terraform plan to detect drift, then terraform apply to re-create it. For more complex cases, terraform refresh updates the state.

What You Learned & What’s Next

Now you can write your first configuration file, run the core Terraform commands, and confidently deploy a real resource. You understand the declarative loop: describe, plan, apply, destroy. You’ve also seen how to handle the most common beginner traps — a skill that will save you hours.

The next lesson dives into variables and outputs — the secret to making your configs reusable. You’ll parameterize your instance type and AMI, then expose the instance’s public IP as an output. That’s the step that turns a one-off script into a real IaC solution. You’re now ready to build.

Practice recap

Create a new directory and write a main.tf that defines a second EC2 instance with a different tag name. Run terraform init, terraform plan, and terraform apply. After confirming it works, run terraform destroy and note that only that instance is removed — your first instance from the lesson stays untouched.

Common mistakes

  • Writing configuration in a file without a .tf extension — Terraform only reads .tf files in the directory.
  • Forgetting the required_providers block and using unversioned providers, which leads to dependency lock drift.
  • Hardcoding an AMI ID without checking if it exists in the target region — always validate region-specific IDs.
  • Running terraform apply without terraform plan first — skipping plan removes your safety net.
  • Using the default AWS region when your credentials are configured for another — set region explicitly.

Variations

  1. Use the -auto-approve flag with terraform apply to skip the interactive prompt in automated pipelines.
  2. Split your configuration into multiple files by resource type (e.g., ec2.tf, vpc.tf) to improve organization.
  3. Use workspaces (named states) to manage multiple environments from the same configuration.

Real-world use cases

  • Deploying a single web server behind a load balancer for a small side project, with the entire setup described in one config file.
  • Creating a disposable test environment (e.g., a database instance) that you destroy after running integration tests, keeping cloud costs low.
  • Onboarding a new developer by having them spin up a private VPC and firewall rules from a version-controlled config, ensuring consistent security posture.

Key takeaways

  • Terraform configuration is declarative: you describe the desired end state, not the steps.
  • A .tf file is built from blocks — terraform, provider, resource — each with a specific role.
  • The core workflow is init → plan → apply → destroy, and plan always comes before apply.
  • Pinning provider versions in the required_providers block prevents dependency drift.
  • Always verify region-specific values like AMI IDs to avoid runtime failures.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.