Courseiva
TF-003Chapter 5 of 15Objective 2.2

Writing and Organizing Terraform Configurations

If you try to manage servers and networks by clicking through a web dashboard, you will eventually make a mistake, lose track of what changed, and waste hours fixing it. That is the problem that writing and organising Terraform configurations solves: it moves your whole infrastructure — every server, every network rule, every database — into a set of plain-text files that you can edit, review, and reuse like a recipe. For the TF-003 exam, understanding how to structure these files is not just a “nice to have” — it is the foundation for every single question about resource creation, state management, and variables.

12 min read
Beginner
Updated Jul 23, 2026
Editorial oversight: Johnson Ajibi· Security Systems Architect · MSc IT Security · IEEE Senior Member

A simple way to picture Writing and Organizing Terraform Configurations

The Recipe Book Analogy

Your kitchen counter, covered in flour and scattered notes. You decide to bake a chocolate cake for a friend’s birthday. Instead of scribbling on scraps, you open a single, well-worn recipe book. This book is your central place for everything: the exact ingredients (flour, cocoa, eggs), the precise steps (preheat oven to 180°C, mix dry ingredients first), and even notes on substitutions (use applesauce instead of oil for a lighter cake).

Now imagine if you wrote the oven temperature on a sticky note, the ingredient list on a napkin, and the mixing instructions on the back of a receipt. When your friend asks, “What’s in this cake?” you can’t easily remember where you put the flour quantity. Making the same cake a second time would be a nightmare.

Terraform configurations work exactly like that recipe book. Instead of clicking around a web console to create a server, you write one main file (like the recipe’s step list) and use separate files for settings (like a “sugars” list or a “flours” list). When you want to change the size of the server, you edit the configuration file — you don’t log into a website and click through menus. This central, organised approach is called “infrastructure as code,” and it’s what makes Terraform powerful.

How It Actually Works

Terraform configurations are written in a language called HCL (HashiCorp Configuration Language). HCL is designed to be human-readable: you do not need to be a programmer to understand it. Every configuration file ends in either .tf or .tf.json. The most common is .tf, which looks like regular text with labels and brackets.

At its simplest, a configuration has three building blocks: resources, providers, and blocks. A *resource* is the thing you want to create — for example, an AWS EC2 instance (a virtual server), an Azure storage account, or a DNS record. A *provider* is the plugin that lets Terraform talk to a specific cloud or service (like AWS, Azure, or Google Cloud). A *block* is a unit of code that starts with a keyword followed by curly braces {}.

Here is a real example:

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

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

In this example, provider "aws" is a block that tells Terraform to use the AWS provider plugin and sets the region to us-east-1. resource "aws_instance" "web_server" is a block that creates a single EC2 instance. The ami (Amazon Machine Image) is like a blueprint for what operating system the server should run, and instance_type defines how powerful the server should be.

Why do we need organisation? A real-world infrastructure might have dozens of resources: several servers, a database, a load balancer, network rules, and storage buckets. Shoving all of that into one giant file is messy and hard to read. That is why Terraform lets you split your configuration into multiple files. For example, you might have:

main.tf — the core resources (servers, databases).

variables.tf — all the values that might change (like server size, region, or user names).

outputs.tf — things you want to see after deployment (like the server’s IP address).

terraform.tfvars — the actual values for your variables (so you do not hardcode them).

This separation is called “modular organisation”. It is not required — Terraform will load every .tf file in the folder — but it makes your configuration easier to read, update, and share with a team.

A critical concept is *variables*. Instead of writing a specific AMI ID directly inside the resource block, you define a variable in variables.tf:

variable "instance_type" {
  description = "Size of the server"
  type        = string
  default     = "t2.micro"
}

Then in main.tf, you reference it:

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type
}

If your team decides to use larger servers, you only change the value in one place — the terraform.tfvars file — instead of editing every resource block.

*Outputs* are like the takeaway from your recipe: they show you the result. After Terraform creates the server, you might want to see its public IP address. In outputs.tf you write:

output "public_ip" {
  value = aws_instance.web_server.public_ip
}

When you run terraform apply, Terraform prints this value to the terminal.

Finally, the *backend* configuration tells Terraform where to store the state file (a record of what it created). By default, it saves to your local disk, but for teams, you often use a remote location like an S3 bucket (AWS) or Azure Storage. The backend configuration is its own block, usually in a file called backend.tf.

To summarise: writing and organising Terraform configurations is about using HCL to describe your infrastructure in a structured, reusable way. You break your configuration into multiple files (main.tf, variables.tf, outputs.tf, terraform.tfvars), use variables to avoid hardcoding, and use outputs to see what you created. This approach replaces manual clicks in a cloud console, reduces errors, and makes your infrastructure auditable.

Flow showing how the common configuration files relate: variable values flow from terraform.tfvars into variable declarations, which are used in main.tf, and the results are exposed via outputs.tf while the state is stored in a backend.

Walk-Through

1

Define the Provider

Start by writing a provider block that tells Terraform which cloud or service you are targeting. For example, provider "aws" { region = "us-east-1" }. This step is mandatory — without it, Terraform does not know where to create resources.

2

Define Variables

Create a variables.tf file and declare every value that might change between deployments (e.g., instance type, region, tags). Give each variable a type (string, number, bool, list, map) and optionally a default. This makes your configuration reusable across different environments.

3

Set Variable Values

Create a terraform.tfvars file and assign actual values to the variables you defined. For example, instance_type = "t3.medium". This separation keeps the configuration clean and allows different value files for development, staging, and production without touching the main code.

4

Write Resource Blocks

In main.tf (or resource-specific files like network.tf), write the resource blocks that define the infrastructure you want. Each block starts with "resource" followed by the resource type and a local name. Inside, you set arguments like ami, instance_type, and security groups.

5

Define Outputs

Create an outputs.tf file to specify what information you want Terraform to display after applying the configuration. For example, output "public_ip" { value = aws_instance.web_server.public_ip }. This helps you see the result without logging into the cloud console.

6

Configure the Backend (Optional but Important for Teams)

Add a backend block to a file like backend.tf to define where Terraform stores its state file. For teams, use a remote backend like an S3 bucket (AWS) or Azure Storage. This ensures everyone on the team sees the same state and avoids conflicts.

What This Looks Like on the Job

Imagine you work for a small e-commerce company that sells handmade furniture. Your boss tells you, “We need a new web server to handle increased traffic during the holiday season.” In the old way, you would log into the AWS Management Console, click “Launch Instance,” select an AMI, choose an instance type, configure security groups, and wait for the server to spin up. You would repeat these exact same clicks every time you needed a new server — and if you missed a setting, you might accidentally expose the database to the public internet.

With Terraform, you do this once. You open your code editor and navigate to your team’s Terraform project folder. Inside, you see the following files:

main.tf — contains the resource "aws_instance" "app_server" block.

variables.tf — defines variables for instance type, AMI, and tags.

terraform.tfvars — sets those variables to specific values: instance_type = "t3.medium", ami = "ami-0abcdef1234567890", tags = { Name = "holiday-server", Owner = "infra-team" }.

outputs.tf — outputs the public IP address and DNS name.

backend.tf — configures the state to be stored in an S3 bucket so anyone on the team can see what exists.

Now, when the boss needs a second server for a different region, you do not start from scratch. You create a new folder called eu-west-1 and copy the same files. You change only the terraform.tfvars to point to a different region and a different AMI for that region. You run terraform init (to download the provider plugin) and then terraform apply. Two minutes later, a new server is live in Europe — with consistent settings.

Later, your colleague realises the security group (a firewall rule) is too permissive. Instead of logging into the console, hunting down each server, and manually updating the rule, they edit the main.tf file, change the security group block, run terraform apply, and every server gets the fix automatically.

This scenario shows what an IT professional actually does: they write configuration files as code, store them in a version control system like Git (so everyone can see the history of changes), and use Terraform commands to apply or destroy resources. The exam expects you to understand that this is not just about syntax — it is about the workflow of defining, sharing, and updating infrastructure in a repeatable way.

How TF-003 Actually Tests This

The TF-003 exam tests you on the structure and syntax of HCL files in several specific ways. Here is what you need to know.

First, recognise that the exam loves to test the difference between *blocks*, *arguments*, and *attributes*. A block is a container with a keyword and curly braces — for example, resource "aws_instance" "my_server" { }. An argument is a key-value pair inside the block, like ami = "ami-xxx". An attribute is a value that Terraform exports after creating the resource, like public_ip. The exam might give you a code snippet and ask, “Which line is an argument?” The answer is the one with an equals sign inside a block.

Second, the exam frequently tests *variable types*. You must know the four basic types: string, number, bool, and list. They also test map (a dictionary of key-value pairs) and object (a more complex structure). A typical question: “You define a variable with type = list(string). Which of the following is a valid value?” The answer format is ["one", "two"]. Be careful: ["one", "two"] is correct; ("one", "two") or {"one", "two"} are wrong.

Third, they test *variable precedence*. Terraform can get variable values from multiple places: default values in the variable block, a terraform.tfvars file, command-line flags like -var, and environment variables. The exam asks, “If the same variable is defined in both terraform.tfvars and as an environment variable, which one wins?” The answer: the command-line flag wins over the environment variable, which wins over terraform.tfvars, which wins over the default.

Fourth, expect questions about *outputs*. The exam wants you to know that outputs must have a value argument. You should also know that you can mark an output as sensitive = true to prevent Terraform from printing its value to the terminal. A common trap question: “What does output "db_password" { value = var.db_password sensitive = true } do?” It prevents the password from being shown in logs, but it is still stored in the state file (so it is not truly secret).

Fifth, they test *file structure and multiple directories*. For example, “You have a directory with three .tf files. When you run terraform apply, does the order of the files matter?” The answer: Terraform processes all .tf files in a directory alphabetically, but resources can reference each other regardless of order. The order of creation depends on dependencies, not file names.

Finally, the exam will test your knowledge of *providers*. You need to know that providers are plugins that give Terraform access to a cloud or service. You must specify at least one provider block. The block usually contains a region argument for cloud providers, but it can also contain authentication settings (like access_key and secret_key for AWS) — though those are often better set via environment variables.

Trap pattern: the exam might present a snippet with an argument outside of a block (like region = "us-east-1" at the top of a file) and ask what is wrong. The answer: arguments must be inside a block — you cannot have a standalone argument.

Another trap: they might give you a variable block with type = bool and a default of "true" (with quotes). A boolean should not be in quotes — it should be default = true (without quotes). The exam expects you to catch these syntax errors.

Key Takeaways

Terraform configurations are written in HCL (HashiCorp Configuration Language), which is human-readable and ends in .tf or .tf.json.

Every resource block defines a single piece of infrastructure and must include the resource type and a local name.

Variables let you avoid hardcoding values; define them in variables.tf and set specific values in terraform.tfvars.

Outputs allow you to extract and display information about created resources, such as IP addresses or DNS names.

Terraform loads all .tf files in a directory alphabetically — file order does not affect resource creation order.

The provider block is required and tells Terraform which cloud or service plugin to use (e.g., AWS, Azure, Google Cloud).

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Variable (input)

Defined with the 'variable' keyword.

Used as an input to your configuration (e.g., server size).

Can have a default value that Terraform uses if no other value is provided.

Output (output)

Defined with the 'output' keyword.

Used to display or export information after resources are created.

Cannot have a default; it always references an attribute or resource.

Resource block

Creates and manages new infrastructure (e.g., a new server).

Uses the keyword 'resource' followed by type and name.

Terraform tracks it in the state file.

Data block

Reads existing infrastructure (e.g., look up an existing AMI).

Uses the keyword 'data' followed by type and name.

Terraform does not create or destroy anything — it only fetches information.

Local value (locals)

Defined with a 'locals' block.

Used to simplify complex expressions (e.g., combining two variable values).

Cannot be overridden from outside (e.g., no command-line flag).

Variable

Defined with a 'variable' block.

Used to accept input from users or other sources.

Can be overridden via terraform.tfvars, environment variables, or command-line flags.

Watch Out for These

Mistake

The order of resources in a .tf file determines the order Terraform creates them.

Correct

Terraform automatically creates a dependency graph based on references (e.g., resource B depends on resource A if B uses an attribute of A). File order does not matter.

Many beginners come from procedural programming where code runs top-to-bottom, so they assume Terraform works the same way.

Mistake

You must put every resource in a single main.tf file for Terraform to work correctly.

Correct

You can (and should) split resources across multiple .tf files. Terraform loads all .tf files in a directory alphabetically and merges them into one configuration.

Beginners often think one file equals one configuration because that is how many other tools (like scripts) work.

Mistake

If you change the name of a resource block (e.g., from 'web_server' to 'app_server'), Terraform will just rename it without affecting the infrastructure.

Correct

Changing the resource name in the block causes Terraform to see the old name as deleted and the new name as new. It will destroy the old resource and create a new one unless you use the 'moved' block or state manipulation.

New users think of the block name as a label, not a unique identifier that Terraform tracks in its state file.

Mistake

Terraform ignores any .tf file that is not called main.tf, variables.tf, or outputs.tf.

Correct

Terraform loads every file ending in .tf in the working directory, regardless of its name. You can name files anything you like (e.g., network.tf, databases.tf).

Many tutorials and exam guides use those three file names, so beginners infer they are mandatory rather than conventions.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

Can I have multiple resource blocks of the same type in one file?

Yes, you can have multiple resource blocks of the same type. Each must have a unique local name (the second label in the resource block). For example, resource "aws_instance" "web_1" and resource "aws_instance" "web_2" are valid.

Do I need a separate file for every resource?

No. You can group resources logically into as few or as many files as you like. Most teams use a main.tf for core resources, network.tf for networking, and databases.tf for databases.

What is the difference between a variable and an output?

A variable is an input you provide to the configuration (like a server size). An output is a value Terraform shows you after it finishes (like the server’s IP address). They are opposite: variables are inputs, outputs are results.

What happens if I delete a resource block from my .tf file and run terraform apply?

Terraform will detect that the resource no longer exists in the configuration and will destroy the corresponding real-world infrastructure (e.g., the server) during the apply.

Can I use the same variable name in multiple .tf files?

Yes, but each variable must be declared exactly once. If you try to declare the same variable name in two files, Terraform will return an error because the variable is duplicated across the configuration.

Do I have to use variables? Can I just hardcode values?

You can hardcode values directly in the resource block, but the exam (and best practice) strongly encourages variables because they make your configuration reusable and easier to update without editing the main code.

Terms Worth Knowing

Keep going

You've finished Writing and Organizing Terraform Configurations. Continue through the TF-003 study guide to build a complete picture of the exam.

Done with this chapter?