Data Sources Explained
A data source is a read-only query that fetches information from a provider or another Terraform state without creating or managing the underlying object. Unlike a resource block, which declares something Terraform should create, update, and destroy, a data block simply asks the provider API 'what does this look like right now?' and exposes the answer as attributes you can reference elsewhere in your configuration. This is essential when infrastructure is partially managed outside Terraform, when you need to look up dynamic values such as the latest AMI ID, or when one Terraform configuration needs to consume outputs produced by another.
Cricket analogy: A data source is like a commentator checking the scoreboard mid-over to report the current score without being able to change it — Terraform reads the AMI ID or account info the same way, purely as a lookup, never as a create-or-manage action.
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = data.aws_subnet.selected.id
}
data "aws_subnet" "selected" {
filter {
name = "tag:Tier"
values = ["public"]
}
}How data sources fit into the dependency graph
Data sources participate in the same dependency graph as resources. Terraform reads a data source during the plan phase, and if that data source references an attribute of a resource that has not been created yet (for example, a security group ID computed at apply time), Terraform defers the read until after that resource is applied. This means a data source can be 'read now' or 'read after apply' depending on whether its arguments depend on unresolved computed values — Terraform figures this out automatically from the graph.
Cricket analogy: Just as an umpire waits for the third umpire's review of a run-out before confirming the score, Terraform defers reading a data source until a not-yet-created security group's ID is available after that resource applies.
Common data source patterns
Three patterns dominate real-world use: querying cloud provider metadata (latest AMI, available availability zones, current account ID via aws_caller_identity), consuming remote state outputs from another Terraform workspace via the terraform_remote_state data source, and looking up existing hand-created or legacy resources (an existing VPC, an existing DNS zone) so new resources can be attached to them without importing them into this state.
Cricket analogy: Like a captain checking the ICC's official player rankings (metadata), pulling last season's stats from a rival team's archive (remote state), and tagging an existing heritage stadium into this season's fixture list without rebuilding it (legacy lookup).
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "acme-tfstate"
key = "network/terraform.tfstate"
region = "us-east-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.network.outputs.private_subnet_id
}Think of a data source as a SELECT query against your infrastructure: it never mutates anything, it just returns rows (attributes) that your configuration can join against, refreshed on every plan unless you pin it with specific filters.
Data sources are re-evaluated on every terraform plan, which means a loosely filtered data source (e.g. 'most recent AMI') can silently change the plan output over time even though you changed nothing in your .tf files — always scope filters tightly for reproducible plans.
- A data source (
datablock) performs a read-only lookup; it never creates, updates, or destroys infrastructure. - Data source arguments can depend on resource attributes, in which case Terraform defers the read until after apply.
terraform_remote_statelets one configuration consume the outputs of another, enabling multi-stack architectures.- Broad filters (like 'most recent AMI') cause plans to change between runs even without config changes — scope filters carefully.
- Data sources are refreshed on every plan by default, keeping referenced values current with real infrastructure state.
- Use data sources to reference resources managed outside your current Terraform state, avoiding unnecessary imports.
Practice what you learned
1. What fundamentally distinguishes a `data` block from a `resource` block in Terraform?
2. When a data source's arguments depend on a resource attribute that is only known after apply, what does Terraform do?
3. Which data source is used to read outputs from a separately managed Terraform state?
4. Why can a data source like 'most recent AMI' cause plan diffs even when no .tf files changed?
5. Which is a valid use case for a data source rather than a resource?
Was this page helpful?
You May Also Like
Resources and Resource Blocks
The resource block is Terraform's fundamental unit of infrastructure declaration, describing a single managed object like a virtual machine, network, or storage bucket.
Remote State Backends
Remote backends store Terraform's state file outside the local disk, enabling team collaboration, durability, and integration with locking mechanisms.
Outputs and Locals
Output values expose data from a module for use by callers or the CLI, while local values let you name and reuse computed expressions within a module. Together they make configurations readable and composable.
Importing Existing Infrastructure
Importing lets Terraform take ownership of resources that already exist in a cloud account, bringing them under state management without recreating them.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics