Dive deep into Terraform providers and resources. Learn how providers connect to cloud APIs, master resource lifecycle management, and work with multiple providers for complex multi-cloud deployments.
Providers are the bridge between Terraform and the real world. When you write a resource block, Terraform doesn't know how to talk to AWS, Azure, or Kubernetes directly - it's the provider that translates your configuration into the correct API calls.
The Terraform provider ecosystem is massive. HashiCorp and the community maintain over 3,000 providers, covering everything from major cloud platforms to niche services like Datadog, PagerDuty, and even Dominos Pizza (yes, really). This ecosystem is one of Terraform's greatest strengths.
In this lesson, you'll learn how providers work under the hood, how to configure them for different scenarios, and how to work with multiple providers in a single configuration. You'll also master resource lifecycle management with meta-arguments like count, for_each, and depends_on.
Providers are plugins that Terraform downloads and uses to interact with remote APIs.
What Providers Do:
Provider Configuration:
1# Specify required providers
2terraform {
3 required_providers {
4 aws = {
5 source = "hashicorp/aws"
6 version = "~> 5.0"
7 }
8 azurerm = {
9 source = "hashicorp/azurerm"
10 version = "~> 3.0"
11 }
12 kubernetes = {
13 source = "hashicorp/kubernetes"
14 version = "~> 2.0"
15 }
16 }
17}
18
19# Configure AWS provider
20provider "aws" {
21 region = "us-east-1"
22
23 # Authentication methods (in order of precedence):
24 # 1. Static credentials (NOT RECOMMENDED)
25 # access_key = "..."
26 # secret_key = "..."
27
28 # 2. Environment variables (RECOMMENDED)
29 # export AWS_ACCESS_KEY_ID="..."
30 # export AWS_SECRET_ACCESS_KEY="..."
31
32 # 3. Shared credentials file (~/.aws/credentials)
33 # profile = "default"
34
35 # 4. IAM role (for EC2, ECS, Lambda)
36 # No configuration needed - automatic
37
38 # 5. Assume role (cross-account)
39 assume_role {
40 role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
41 session_name = "TerraformSession"
42 }
43}
44
45# Configure Azure provider
46provider "azurerm" {
47 features {}
48 subscription_id = var.azure_subscription_id
49}
50
51# Configure Kubernetes provider
52provider "kubernetes" {
53 config_path = "~/.kube/config"
54 context = "my-cluster"
55}Version Constraints:
| Constraint | Meaning |
|------------|---------|
| = 5.0.0 | Exactly version 5.0.0 |
| >= 5.0.0 | 5.0.0 or newer |
| ~> 5.0 | >= 5.0, < 6.0 (minor updates OK) |
| ~> 5.0.0 | >= 5.0.0, < 5.1.0 (patch updates OK) |
| >= 5.0, < 6.0 | Explicit range |
Real-world infrastructure often spans multiple regions or accounts. Provider aliases let you use the same provider type multiple times.
Multi-Region Deployment:
1# Default provider (us-east-1)
2provider "aws" {
3 region = "us-east-1"
4}
5
6# Aliased provider for us-west-2
7provider "aws" {
8 alias = "west"
9 region = "us-west-2"
10}
11
12# Aliased provider for eu-west-1
13provider "aws" {
14 alias = "europe"
15 region = "eu-west-1"
16}
17
18# Use default provider
19resource "aws_instance" "primary" {
20 ami = "ami-east-123"
21 instance_type = "t2.micro"
22}
23
24# Use west provider
25resource "aws_instance" "secondary" {
26 provider = aws.west
27 ami = "ami-west-456"
28 instance_type = "t2.micro"
29}
30
31# Use europe provider
32resource "aws_instance" "tertiary" {
33 provider = aws.europe
34 ami = "ami-eu-789"
35 instance_type = "t2.micro"
36}Multi-Account Deployment:
1# Production account
2provider "aws" {
3 alias = "prod"
4 region = "us-east-1"
5
6 assume_role {
7 role_arn = "arn:aws:iam::111111111111:role/TerraformRole"
8 }
9}
10
11# Development account
12provider "aws" {
13 alias = "dev"
14 region = "us-east-1"
15
16 assume_role {
17 role_arn = "arn:aws:iam::222222222222:role/TerraformRole"
18 }
19}
20
21# VPC in production
22resource "aws_vpc" "prod" {
23 provider = aws.prod
24 cidr_block = "10.0.0.0/16"
25}
26
27# VPC in development
28resource "aws_vpc" "dev" {
29 provider = aws.dev
30 cidr_block = "10.1.0.0/16"
31}Passing Providers to Modules:
1module "vpc_west" {
2 source = "./modules/vpc"
3
4 providers = {
5 aws = aws.west
6 }
7
8 cidr_block = "10.0.0.0/16"
9}Understanding how Terraform manages resource lifecycle helps you write better configurations and troubleshoot issues.
Resource Actions:
Terraform can perform four actions on resources:
| Action | Symbol | When |
|--------|--------|------|
| Create | + | Resource doesn't exist |
| Update | ~ | Attribute changed (in-place) |
| Replace | -/+ | Force-new attribute changed |
| Destroy | - | Resource removed from config |
Lifecycle Meta-Argument:
1resource "aws_instance" "web" {
2 ami = var.ami_id
3 instance_type = var.instance_type
4
5 lifecycle {
6 # Create new resource before destroying old one
7 create_before_destroy = true
8
9 # Prevent accidental destruction
10 prevent_destroy = true
11
12 # Ignore changes to specific attributes
13 ignore_changes = [
14 tags,
15 user_data,
16 ]
17
18 # Replace when expression is true
19 replace_triggered_by = [
20 aws_security_group.web.id
21 ]
22 }
23}When to Use Each:
create_before_destroy:
prevent_destroy:
ignore_changes:
Replace Triggers:
1resource "null_resource" "deployment" {
2 triggers = {
3 # Replace when these change
4 image_tag = var.image_tag
5 config_hash = md5(file("config.json"))
6 }
7
8 provisioner "local-exec" {
9 command = "kubectl rollout restart deployment/app"
10 }
11}Meta-arguments let you create multiple similar resources without repetition.
Count: Create N Copies:
1# Create 3 identical instances
2resource "aws_instance" "web" {
3 count = 3
4
5 ami = var.ami_id
6 instance_type = "t2.micro"
7
8 tags = {
9 Name = "web-server-${count.index}" # web-server-0, web-server-1, web-server-2
10 }
11}
12
13# Reference specific instance
14output "first_instance_ip" {
15 value = aws_instance.web[0].public_ip
16}
17
18# Reference all instances
19output "all_instance_ips" {
20 value = aws_instance.web[*].public_ip # Splat expression
21}Conditional Resources with Count:
1variable "create_monitoring" {
2 type = bool
3 default = true
4}
5
6# Only create if variable is true
7resource "aws_cloudwatch_metric_alarm" "high_cpu" {
8 count = var.create_monitoring ? 1 : 0
9
10 alarm_name = "high-cpu"
11 # ... other config
12}For_Each: Create from Map or Set:
1variable "instances" {
2 type = map(object({
3 instance_type = string
4 ami = string
5 }))
6 default = {
7 "web" = {
8 instance_type = "t2.micro"
9 ami = "ami-web123"
10 }
11 "api" = {
12 instance_type = "t2.small"
13 ami = "ami-api456"
14 }
15 "worker" = {
16 instance_type = "t2.medium"
17 ami = "ami-worker789"
18 }
19 }
20}
21
22resource "aws_instance" "servers" {
23 for_each = var.instances
24
25 ami = each.value.ami
26 instance_type = each.value.instance_type
27
28 tags = {
29 Name = "${each.key}-server" # web-server, api-server, worker-server
30 }
31}
32
33# Reference specific instance
34output "web_ip" {
35 value = aws_instance.servers["web"].public_ip
36}Count vs For_Each:
| Aspect | Count | For_Each |
|--------|-------|----------|
| Index | Numeric (0, 1, 2) | String key |
| Best for | Identical resources | Different configurations |
| Removal | Shifts indices | Safe, by key |
| State | resource[0] | resource["key"] |
Terraform automatically builds a dependency graph to determine the correct order for creating resources.
Implicit Dependencies:
Terraform creates implicit dependencies when you reference one resource from another:
1# VPC is created first (no dependencies)
2resource "aws_vpc" "main" {
3 cidr_block = "10.0.0.0/16"
4}
5
6# Subnet depends on VPC (implicit)
7resource "aws_subnet" "public" {
8 vpc_id = aws_vpc.main.id # <- Creates dependency
9 cidr_block = "10.0.1.0/24"
10}
11
12# Instance depends on Subnet (implicit)
13resource "aws_instance" "web" {
14 subnet_id = aws_subnet.public.id # <- Creates dependency
15 ami = "ami-123"
16 instance_type = "t2.micro"
17}
18
19# Order: VPC → Subnet → InstanceExplicit Dependencies with depends_on:
Sometimes dependencies aren't captured by references:
1# IAM role (no direct reference from instance)
2resource "aws_iam_role" "app" {
3 name = "app-role"
4 assume_role_policy = jsonencode({
5 Version = "2012-10-17"
6 Statement = [{
7 Action = "sts:AssumeRole"
8 Effect = "Allow"
9 Principal = {
10 Service = "ec2.amazonaws.com"
11 }
12 }]
13 })
14}
15
16resource "aws_iam_instance_profile" "app" {
17 name = "app-profile"
18 role = aws_iam_role.app.name
19}
20
21# Instance needs role to exist first
22resource "aws_instance" "app" {
23 ami = "ami-123"
24 instance_type = "t2.micro"
25 iam_instance_profile = aws_iam_instance_profile.app.name
26
27 # Explicit dependency on role
28 depends_on = [aws_iam_role.app]
29
30 user_data = <<-EOF
31 #!/bin/bash
32 # Uses IAM role permissions
33 aws s3 ls
34 EOF
35}Viewing the Dependency Graph:
1# Generate DOT format graph
2terraform graph
3
4# Save as PNG (requires graphviz)
5terraform graph | dot -Tpng > graph.png
6
7# View as SVG
8terraform graph | dot -Tsvg > graph.svgGraph Output Example:
1digraph {
2 "aws_vpc.main" -> "aws_subnet.public"
3 "aws_subnet.public" -> "aws_instance.web"
4 "aws_security_group.web" -> "aws_instance.web"
5}Let's explore configurations for the most commonly used providers.
AWS Provider:
1provider "aws" {
2 region = "us-east-1"
3
4 default_tags {
5 tags = {
6 Environment = var.environment
7 Project = var.project_name
8 ManagedBy = "Terraform"
9 }
10 }
11}
12
13# VPC with subnets
14resource "aws_vpc" "main" {
15 cidr_block = "10.0.0.0/16"
16 enable_dns_hostnames = true
17 enable_dns_support = true
18}
19
20resource "aws_subnet" "public" {
21 count = 3
22 vpc_id = aws_vpc.main.id
23 cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
24 availability_zone = data.aws_availability_zones.available.names[count.index]
25
26 map_public_ip_on_launch = true
27}Azure Provider:
1provider "azurerm" {
2 features {}
3}
4
5resource "azurerm_resource_group" "main" {
6 name = "${var.project_name}-rg"
7 location = "East US"
8}
9
10resource "azurerm_virtual_network" "main" {
11 name = "${var.project_name}-vnet"
12 address_space = ["10.0.0.0/16"]
13 location = azurerm_resource_group.main.location
14 resource_group_name = azurerm_resource_group.main.name
15}
16
17resource "azurerm_linux_virtual_machine" "main" {
18 name = "${var.project_name}-vm"
19 resource_group_name = azurerm_resource_group.main.name
20 location = azurerm_resource_group.main.location
21 size = "Standard_DS1_v2"
22 admin_username = "adminuser"
23
24 # ... additional configuration
25}Google Cloud Provider:
1provider "google" {
2 project = var.project_id
3 region = "us-central1"
4}
5
6resource "google_compute_network" "vpc" {
7 name = "${var.project_name}-vpc"
8 auto_create_subnetworks = false
9}
10
11resource "google_compute_instance" "vm" {
12 name = "${var.project_name}-instance"
13 machine_type = "e2-medium"
14 zone = "us-central1-a"
15
16 boot_disk {
17 initialize_params {
18 image = "debian-cloud/debian-11"
19 }
20 }
21
22 network_interface {
23 network = google_compute_network.vpc.name
24 access_config {}
25 }
26}Kubernetes Provider:
1provider "kubernetes" {
2 config_path = "~/.kube/config"
3}
4
5resource "kubernetes_namespace" "app" {
6 metadata {
7 name = var.namespace
8 }
9}
10
11resource "kubernetes_deployment" "app" {
12 metadata {
13 name = "app"
14 namespace = kubernetes_namespace.app.metadata[0].name
15 }
16
17 spec {
18 replicas = 3
19
20 selector {
21 match_labels = {
22 app = "myapp"
23 }
24 }
25
26 template {
27 metadata {
28 labels = {
29 app = "myapp"
30 }
31 }
32
33 spec {
34 container {
35 image = "nginx:latest"
36 name = "app"
37 }
38 }
39 }
40 }
41}A complete example showing how to deploy infrastructure across multiple AWS regions using provider aliases and for_each.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Terraform Providers & Resources - Demo</title>
7 <style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body {
10 font-family: 'Segoe UI', sans-serif;
11 background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
12 min-height: 100vh;
13 padding: 20px;
14 }
15 .container {
16 max-width: 1200px;
17 margin: 0 auto;
18 background: white;
19 border-radius: 15px;
20 box-shadow: 0 20px 60px rgba(0,0,0,0.3);
21 overflow: hidden;
22 }
23 header {
24 background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
25 color: white;
26 padding: 30px;
27 text-align: center;
28 }
29 h1 { font-size: 2rem; }
30 .content { padding: 30px; }
31 .section { margin-bottom: 40px; }
32 .section-title {
33 color: #1e40af;
34 font-size: 1.4rem;
35 margin-bottom: 20px;
36 border-bottom: 3px solid #1e40af;
37 padding-bottom: 10px;
38 }
39
40 .grid-2 {
41 display: grid;
42 grid-template-columns: 1fr 1fr;
43 gap: 20px;
44 }
45
46 .card {
47 background: #f8f9fa;
48 border-radius: 10px;
49 padding: 20px;
50 }
51
52 .card h3 {
53 color: #1e40af;
54 margin-bottom: 15px;
55 }
56
57 .provider-flow {
58 display: flex;
59 align-items: center;
60 justify-content: center;
61 gap: 20px;
62 padding: 30px;
63 background: #f8f9fa;
64 border-radius: 10px;
65 }
66
67 .flow-box {
68 padding: 20px;
69 border-radius: 8px;
70 text-align: center;
71 min-width: 120px;
72 }
73
74 .flow-hcl { background: #a78bfa; color: white; }
75 .flow-provider { background: #1e40af; color: white; }
76 .flow-api { background: #10b981; color: white; }
77 .flow-arrow { font-size: 2rem; color: #1e40af; }
78
79 .meta-grid {
80 display: grid;
81 grid-template-columns: repeat(3, 1fr);
82 gap: 15px;
83 }
84
85 .meta-card {
86 background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
87 color: white;
88 padding: 20px;
89 border-radius: 10px;
90 text-align: center;
91 }
92
93 .meta-card h4 { margin-bottom: 10px; }
94
95 code {
96 display: block;
97 background: #1e293b;
98 color: #22d3ee;
99 padding: 15px;
100 border-radius: 8px;
101 font-size: 0.85rem;
102 overflow-x: auto;
103 white-space: pre;
104 }
105
106 @media (max-width: 768px) {
107 .grid-2, .meta-grid { grid-template-columns: 1fr; }
108 .provider-flow { flex-direction: column; }
109 }
110 </style>
111</head>
112<body>
113 <div class="container">
114 <header>
115 <h1>🔌 Terraform Providers & Resources</h1>
116 <p>Connecting Terraform to the Cloud</p>
117 </header>
118
119 <div class="content">
120 <div class="section">
121 <h2 class="section-title">How Providers Work</h2>
122 <div class="provider-flow">
123 <div class="flow-box flow-hcl">
124 <strong>HCL Code</strong>
125 <div>main.tf</div>
126 </div>
127 <div class="flow-arrow">→</div>
128 <div class="flow-box flow-provider">
129 <strong>Provider</strong>
130 <div>hashicorp/aws</div>
131 </div>
132 <div class="flow-arrow">→</div>
133 <div class="flow-box flow-api">
134 <strong>Cloud API</strong>
135 <div>api.aws.amazon.com</div>
136 </div>
137 </div>
138 </div>
139
140 <div class="section">
141 <h2 class="section-title">Provider Aliases</h2>
142 <div class="grid-2">
143 <div class="card">
144 <h3>Multi-Region</h3>
145 <code>provider "aws" {
146 alias = "west"
147 region = "us-west-2"
148}
149
150resource "aws_instance" "west" {
151 provider = aws.west
152 ...
153}</code>
154 </div>
155 <div class="card">
156 <h3>Multi-Account</h3>
157 <code>provider "aws" {
158 alias = "prod"
159 assume_role {
160 role_arn = "arn:...prod..."
161 }
162}
163
164resource "aws_vpc" "prod" {
165 provider = aws.prod
166 ...
167}</code>
168 </div>
169 </div>
170 </div>
171
172 <div class="section">
173 <h2 class="section-title">Meta-Arguments</h2>
174 <div class="meta-grid">
175 <div class="meta-card">
176 <h4>count</h4>
177 <p>Create N identical resources</p>
178 <small>count = 3</small>
179 </div>
180 <div class="meta-card">
181 <h4>for_each</h4>
182 <p>Create from map or set</p>
183 <small>for_each = var.instances</small>
184 </div>
185 <div class="meta-card">
186 <h4>depends_on</h4>
187 <p>Explicit dependencies</p>
188 <small>depends_on = [aws_iam_role.x]</small>
189 </div>
190 </div>
191 </div>
192
193 <div class="section">
194 <h2 class="section-title">Lifecycle Rules</h2>
195 <div class="card">
196 <code>lifecycle {
197 create_before_destroy = true # Zero-downtime replacement
198 prevent_destroy = true # Prevent accidental deletion
199 ignore_changes = [tags] # Ignore external changes
200}</code>
201 </div>
202 </div>
203 </div>
204 </div>
205</body>
206</html>This demonstration covers provider and resource concepts including how providers bridge Terraform to cloud APIs, using aliases for multi-region and multi-account deployments, meta-arguments (count, for_each, depends_on), and lifecycle rules for resource management.
Excellent! You now understand how providers and resources work together. Next, we'll dive into Terraform State Management - the critical concept of how Terraform tracks your infrastructure and why proper state handling is essential for team collaboration.
Your progress is saved in this browser, on this device only.