Skip to main content

Command Palette

Search for a command to run...

Day 2 Of 30 Days Of AWS Terraform: Providers and Version Management

Updated
5 min read
Day 2 Of 30 Days Of AWS Terraform: Providers and Version Management

Introduction

Continuing my journey into the #30DaysOfAWSTerraform challenge, Day 2 shifted from conceptual foundations into the first real Terraform building blocks — providers, configuration blocks, and declaring resources.

If you are new to this series, Day 1 focused on understanding what. Terraform is, Infrastructure as Code, and the basic Terraform workflow.

Day 2 moves one level deeper. This is where Terraform stops being just configuration files and starts behaving like a system that expects discipline.

Terraform Providers: How Terraform Talks to the Cloud

Terraform does not communicate with AWS, Azure, or any cloud platform directly.
It relies on providers.

Providers translate Terraform configuration into cloud API calls.

A provider is essentially a plugin that understands how to translate Terraform configuration into cloud-specific API calls. When we say Terraform is cloud-agnostic, providers are the reason why.

Without a provider — Terraform is just a language and a workflow.
With a provider — Terraform becomes capable of creating real infrastructure.

A simple provider configuration looks like this

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

This tells Terraform which cloud platform to talk to and where. At this point, Terraform still doesn’t know which version of the provider it should trust. That question becomes important very quickly.

Terraform Core Version vs Provider Version

One of the first confusing things on Day 2 is realizing that Terraform itself has a version, and providers also have their own versions.

They are related, but they are not the same.

  • Terraform core version
    This is the Terraform CLI you install on your machine. It controls how Terraform parses files, builds dependency graphs, and executes plans.

  • Provider version
    This controls how Terraform interacts with a specific platform, such as AWS APIs.

Terraform core is the engine.
Providers are the plugins.

This separation gives flexibility, but it also introduces risk if versions are not managed properly.

Terraform Core and providers are versioned independently.

Why Versioning Matters in Terraform

A natural question at this stage is:
Why not just use the latest version of everything?

The answer becomes clear when you think about Infrastructure as Code seriously.

Cloud APIs evolve. Providers evolve. Behavior changes. If Terraform silently upgrades a provider, your infrastructure could behave differently without you changing a single line of code.

That breaks one of the core promises of IaC: predictability.

Terraform’s solution to this problem is explicit version control.

Version Constraints: Making Infrastructure Predictable

Terraform allows you to define version constraints using the terraform block.

terraform {required_version = ">= 1.0.0"
required_providers {
aws = {
    source = "hashicorp/aws"
    version = "~> 6.0"
      }
    }
}

This block does several important things at once:

  • Ensures Terraform runs only on compatible CLI versions

  • Locks the provider to a safe version range

  • Prevents accidental upgrades that could introduce breaking changes

Terraform evaluates these constraints during terraform init.
If versions don’t match, Terraform refuses to proceed.

This may feel strict at first, but it’s intentional.

Understanding Version Constraint Operators

Terraform supports several operators for version constraints, but the most commonly used ones are:

  • = → exact version

  • >= → minimum acceptable version

  • ~> → pessimistic constraint

The ~> operator is especially important:

version = "~> 5.0"

This allows updates within the same major version (for example, 5.1, 5.2) but blocks breaking changes like 6.0.

In practice, this strikes a balance between:

  • Stability

  • Receiving safe improvements and fixes

This is why you’ll often see ~> recommended in real Terraform codebases.

Resource Blocks: Where Infrastructure Is Actually Defined

Once the provider is configured and version constraints are in place, Terraform is finally ready to describe real infrastructure. This is done using resource blocks.

A resource block defines what Terraform should create and what the desired state should look like. Terraform does not create anything immediately; it records intent and acts only when the workflow is executed.

resource "aws_instance" "example" {ami = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1)instance_type = "t2.micro"
tags = {Name = "Terraform-Day2-EC2"Environment = "Learning"}}

In this block:

  • aws_s3_bucket is the resource type provided by the AWS provider

  • example is a local name Terraform uses to track this resource

  • The configuration inside the block describes the desired state

What’s important is the dependency chain here. Resource blocks rely on providers. Without the AWS provider being configured and versioned correctly, Terraform would not know how to interpret or create this resource.

This is where the earlier discussion about providers and versions becomes concrete.

Providers define how Terraform talks to the cloud.

Resource blocks define what Terraform wants to exist.

Providers and Modules: Where Versions Belong

Once providers and versions enter the picture, another design question appears:
Should provider configuration live inside modules or outside?

The recommended approach is:

  • Provider configuration and version constraints live in the root module

  • Reusable modules only declare which providers they require, without pinning versions

Inside a module, you might see:

terraform {
        required_providers {
        aws = {source = "hashicorp/aws"
        }
    }
}

This keeps modules reusable across

Different regions

Different accounts

Different provider versions

Version control stays centralized and intentional.

Provider configuration belongs in the root module.

Best Practices That Start Making Sense on Day 2

Day 2 makes several Terraform best practices feel logical rather than arbitrary:

  • Pin provider versions early

  • Never rely on implicit “latest” versions

  • Keep provider configuration in the root module

  • Treat Terraform code like application code, not scripts

  • Let terraform init enforce consistency

These practices matter more as infrastructure grows and more people interact with the same codebase.

What Changed for Me on Day 2

Day 1 made Terraform understandable.
Day 2 made Terraform serious.

Providers and version constraints introduced the idea that Infrastructure as Code is not just about automation, but about control and predictability over time.

Terraform started feeling less forgiving, and that’s exactly why it’s trusted in real systems.

Closing Thoughts

Day 2 didn’t add much visible infrastructure, but it added something more important: structure.

Understanding providers and version management early prevents subtle problems later, when real resources and real costs are involved.

Huge thanks to Piyush Sachdeva for designing the challenge in a way that builds understanding before complexity.

This challenge is slowly reinforcing the idea that good infrastructure isn’t just created, it’s maintained intentionally.