TerraformVII · Resources, Data Sources, and count/for_eachData sources
Data Sources: Reading Infrastructure Terraform Does Not Manage
What you'll learn
- Distinguish managed resources from data sources
- Read existing infrastructure via data sources
- Use data sources to bridge configuration and existing resources
- Recognise when data sources are the right tool
Prerequisites
- 18-resources
- Resources: The Building Blocks
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
A data source is a read-only reference to information that Terraform does not manage. Data sources are the bridge between the configuration and the real world when Terraform does not own the resource. This lesson teaches when data sources are the right tool, and how to use them safely.
Managed vs data sources
A managed resource is something Terraform owns:
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
# ...
}
A data source is something Terraform reads:
data "aws_ami" "ubuntu" {
most_recent = true
# ...
}
The difference:
| Aspect | Managed resource | Data source |
|---|---|---|
| CRUD | Terraform creates, reads, updates, deletes | Terraform reads only |
| State | The resource is in state | The data source is in state, but no attributes are stored |
| Drift | Terraform detects and reconciles | Terraform only reads the current value |
| Production warnings | Affects real-world resources | No side effects |
When to use a data source
A data source is appropriate when:
- The resource exists outside Terraform.
- The resource is managed by another tool.
- The resource is read-only (e.g. an AMI ID).
- The resource is dynamic (e.g. the latest AMI).
A data source is not appropriate when:
- The resource should be managed by Terraform (use
resource). - The resource is dynamic and you want Terraform to track it
(use
resource). - The resource is a static configuration value (use
variable).
Common data sources
The most common data sources are:
aws_ami— the latest Amazon Machine Image.aws_caller_identity— the current AWS account.aws_region— the current AWS region.aws_availability_zones— the AZs in the current region.azurerm_subscription— the current Azure subscription.google_project— the current GCP project.kubernetes_namespace— a Kubernetes namespace.github_repository— a GitHub repository.tls_public_key— a public key generated from a private key.
The provider documentation is the source of truth for the full list.
How to use a data source
A data source is declared with the data block:
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
The data source is referenced via data.<type>.<name>.<attribute>:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
# ...
}
The data source is read at plan time. The attribute is the providers response.
The output of a data source
A data source has attributes. The attributes are provider-specific:
output "ami_id" {
value = data.aws_ami.ubuntu.id
description = "The AMI ID used for the web server."
}
The data.aws_ami.ubuntu.id is the AMI ID. The output makes
the value available to other configurations.
The state of a data source
A data source is in the state. The state records that the data source was read:
"data_sources": [
{
"type": "aws_ami",
"name": "ubuntu",
"provider": "...",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "ami-0abc123def456789",
"name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210401",
# ...
}
}
]
}
]
The state records the data sources attributes as of the last refresh. The state is refreshed at plan time.
The dangerous pattern
A data source that reads the latest AMI is dangerous:
data "aws_ami" "ubuntu" {
most_recent = true
}
The most_recent = true means “the AMI with the latest
creation date”. A new AMI is published, and the next plan
proposes to replace the instance because the AMI changed.
The fix is to pin the AMI by ID:
data "aws_ami" "ubuntu" {
filter {
name = "image-id"
values = ["ami-0e1bed4f"]
}
}
The course has a dedicated lesson on provider upgrade unexpected plans (Part XCV).
What comes next
The next lesson is managed vs unmanaged infrastructure — the operational distinction between what Terraform owns and what it reads.
Verification
Knowledge check · 7 questions
Q1. What is a resource address?
Q2. When does a resource trigger replacement vs in-place update?
Q3. `count` provides stable resource identity.
Q4. When is for_each preferred over count?
Q5. Which of the following are data sources used for? (Select all that apply.)
Q6. What is the role of `lifecycle.ignore_changes`?
Q7. A resource shows a +20 to change in the plan for an attribute that should be stable. What is the most likely cause?
Passing score: 75%. Answers are checked in this browser.