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
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"orinstance_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
- Create a project directory – each Terraform deployment gets its own folder. Inside, you’ll keep your
.tffiles, state, and related scripts. A dedicated directory isolates environments and teams. - Write the basic configuration file – name it
main.tf. Start with aterraformblock that pins the required providers, then add aproviderblock for your cloud, and finally aresourceblock that describes the exact infrastructure you want. This wired order ensures Terraform knows what to install before how to install it. - Initialize the working directory – run
terraform initin the terminal. This downloads provider plugins and sets up the backend. You’ll see a success message and a fresh.terraformfolder — that’s your dependency cache. - 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. - Apply the configuration – run
terraform apply. Terraform asks for confirmation, executes the plan, and reports the created resources. Your infrastructure is now live. - Clean up when done – use
terraform destroyto 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
terraformblock withrequired_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
.tfextension — Terraform only reads.tffiles in the directory. - Forgetting the
required_providersblock 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 applywithoutterraform planfirst — skipping plan removes your safety net. - Using the default AWS region when your credentials are configured for another — set
regionexplicitly.
Variations
- Use the
-auto-approveflag withterraform applyto skip the interactive prompt in automated pipelines. - Split your configuration into multiple files by resource type (e.g.,
ec2.tf,vpc.tf) to improve organization. - 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
.tffile is built from blocks —terraform,provider,resource— each with a specific role. - The core workflow is
init → plan → apply → destroy, andplanalways comes beforeapply. - Pinning provider versions in the
required_providersblock prevents dependency drift. - Always verify region-specific values like AMI IDs to avoid runtime failures.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.