Terraform

Terraform Core Concepts and Syntax: A Beginner’s Guide

AGAnurag Gupta05 Apr 2025 · Updated 04 Oct 2026 · 6 min read
Terraform Core Concepts and Syntax: A Beginner’s Guide

Quick answer: Terraform configurations are built from a small set of building blocks — providers, resources, data sources, variables, outputs, modules and state — written in HashiCorp Configuration Language (HCL). Once you understand what each block does and how they reference each other, you can read almost any Terraform project, no matter how large.

This guide introduces every core concept with a working example, shows the HCL syntax rules that trip up newcomers, and ends with a complete mini-project that ties the pieces together. You need no prior Terraform experience, only a basic idea of what a cloud server or storage bucket is.

First, the mental model. Everything in Terraform revolves around three things. Your .tf files describe the desired state. The state file records what Terraform created last time. A plan is the calculated difference between the two. terraform apply executes that difference through provider plugins, then updates the state. Keep this triangle in mind and each concept below will slot into place.

Providers: the plugins that talk to platforms

Terraform’s core binary understands HCL and dependency graphs, nothing else. A provider is a plugin that knows how to call a specific API — AWS, Azure, Google Cloud, Kubernetes, GitHub, Cloudflare. You declare which providers you need and how to configure them:

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}

Credentials never go here. The AWS provider picks them up from environment variables, the ~/.aws config, or an IAM role attached to the machine. We covered provider internals in more depth in Top Advantages of Using Terraform for Infrastructure as Code.

Resources and data sources: creating versus reading

A resource block describes one object Terraform should manage. It has a type (which provider and which object), a local name you choose, and a body of arguments:

resource "aws_s3_bucket" "assets" {
  bucket = "tkh-assets-2025"

  tags = {
    Environment = "dev"
    Owner       = "platform-team"
  }
}

The type aws_s3_bucket is prefixed with the provider name, so Terraform always knows which plugin handles it. The local name assets exists only inside your code; elsewhere you refer to this bucket as aws_s3_bucket.assets, and to its attributes as aws_s3_bucket.assets.arn or aws_s3_bucket.assets.id.

A data source, by contrast, looks up information without creating anything. The classic example is finding the latest AMI so you never hard-code an image ID:

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]   # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

Data sources are referenced with the data. prefix. Use them to read VPCs created by another team, your current account ID (aws_caller_identity), or secrets stored in a vault.

Variables and outputs: inputs and results

Input variables make a configuration reusable across environments. Outputs expose values after apply, both to humans and to other Terraform configurations.

variable "environment" {
  type        = string
  description = "Deployment environment name"
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be dev, staging or prod."
  }
}

output "bucket_name" {
  value       = aws_s3_bucket.assets.bucket
  description = "Name of the assets bucket"
}

Variables are referenced as var.environment and can be set from a terraform.tfvars file, the -var flag, or TF_VAR_environment environment variables. The full story, including types, locals and functions, is in Using Variables and Expressions in Terraform.

Modules: reusable bundles of configuration

Any folder of .tf files is a module. The folder you run commands in is the root module; it can call child modules from a local path, Git, or the Terraform Registry:

module "network" {
  source = "./modules/network"

  vpc_cidr    = "10.0.0.0/16"
  environment = var.environment
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = module.network.public_subnet_id
}

A module’s outputs are read as module.<name>.<output>. Modules are how teams avoid copying the same 40 lines of VPC code into every project.

State: Terraform’s memory

After every apply, Terraform writes terraform.tfstate, a JSON file mapping each resource in your code to a real object ID in the cloud. It is how Terraform knows that aws_instance.web is i-0a1b2c3d and that nothing needs to change on the next run. State can contain sensitive values, so in teams it is stored in a remote backend (S3, Azure Blob, GCS, HCP Terraform) with locking, never in Git.

HCL syntax rules you must know

Element Syntax Example
Argument name = value instance_type = "t3.micro"
Block type "label" { ... } resource "aws_vpc" "main" { }
Nested block No = sign tags { } vs. tags = { } (map argument)
Reference type.name.attribute aws_vpc.main.id
Interpolation "text ${expr}" "web-${var.environment}"
Comment # or // or /* */ # Mumbai region
Lists and maps [ ] and { } ["a", "b"], { key = "v" }

Two habits save hours: run terraform fmt to standardise spacing, and terraform validate to catch typos before you plan. Terraform reads every .tf file in the directory and merges them, so splitting into main.tf, variables.tf and outputs.tf is purely for human readability.

Common beginner mistakes

  1. Confusing the resource type with the local name. aws_instance is fixed by the provider; web is yours. Renaming web later makes Terraform think you deleted one resource and created another.
  2. Using the old "${var.x}" interpolation everywhere. Since Terraform 0.12 you write var.x directly; wrap in ${} only inside a longer string.
  3. Hard-coding IDs that a data source could look up, such as AMIs or VPC IDs that differ between accounts.
  4. Committing terraform.tfstate to Git. It leaks secrets and causes merge conflicts. Use a remote backend and add *.tfstate* to .gitignore.
  5. Skipping plan. terraform apply shows a plan too, but reading it in a hurry at the approval prompt is how people destroy databases.

Frequently asked questions

Is HCL the same as JSON or YAML?

No. HCL is HashiCorp’s own language, designed to be readable by humans while also being machine-friendly. Terraform does accept a JSON variant (.tf.json) for generated configs, but almost everyone writes HCL.

What is the difference between a resource and a data source?

A resource is created, updated and destroyed by Terraform. A data source only reads an existing object. If you delete a data block, nothing in the cloud is removed.

Do I need a separate state file per environment?

Yes, in practice. Use separate backend paths or workspaces for dev, staging and prod so that a mistake in one cannot affect the others.

Which Terraform version should I learn on?

Any 1.x release. The language has been stable since 1.0 (2021), and newer versions only add features such as import blocks, moved blocks and native testing.

Key takeaways

  • Providers connect Terraform to APIs; resources create things; data sources read things.
  • Variables make configs reusable, outputs expose results, modules package everything for reuse.
  • State is the link between code and reality — store it remotely and never in Git.
  • HCL has only a handful of syntax rules; terraform fmt and validate enforce them for you.

Ready to turn these concepts into real AWS and Azure deployments? Our DevOps course covers Terraform end to end with live projects, mentor support and placement assistance. Prefer video? Follow along on our YouTube channel.

AG
Written byAnurag Gupta

Part of the Techknowledgehub team of industry mentors, writing practical guides to help you build a job-ready tech career.

More articles by Anurag Gupta →
Keep reading

Related articles

Leave a Reply