# terraform / data-sources / lookups

Fetch What Already Exists: Data Sources

Not every piece of infrastructure is created by your current Terraform code. Data sources allow Terraform to query existing cloud resources — like the latest Ubuntu AMI, corporate VPCs, active subnets, or your AWS Account ID — and use their attributes without taking over their lifecycle.

data "aws_ami" "ubuntu"READ ONLY
most_recenttrue
owners["099720109477"] (Canonical)
returns.id"ami-03f4878755434977f"
0
Resources Created
100%
Multi-Region Safe
API
Live Lookup
# paradigm

Read-Only Infrastructure

In real production teams, infrastructure is layered. The network engineering team might build the VPC and subnets, the security team provides standard base AMIs, and your team provisions application servers inside that network. Data sources let you wire these pieces together effortlessly.

Aspectresource blockdata block
OperationCreate, Update, DestroyRead & Query only
Lifecycle ControlTerraform owns the resourceTerraform never touches or deletes it
State FileTracks configuration & IDsCaches queried attributes
Destructionterraform destroy terminates itIgnored during destroy
the problem

Why Hardcoding IDs Fails

An AMI ID like ami-0c55b159cbfafe1f0 only exists in us-east-1. If your deployment switches to ap-south-1 (Mumbai) or eu-west-1 (Ireland), your Terraform build will crash. Data sources query the provider API dynamically in whatever region is currently active.

# syntax

Data Block Structure

main.tfDATA BLOCK FORMAT
1 2 3 4 5 6 7 8 9
data "TYPE" "LOCAL_NAME" {
  # Filter arguments to find the target
  argument_1 = "value"
}

# Reference anywhere in the configuration:
resource "aws_instance" "app" {
  ami = data.TYPE.LOCAL_NAME.id
}
prefix

data.TYPE.LOCAL_NAME.ATTRIBUTE

Every data source is accessed via the data. prefix. This makes it instantly obvious when reading code that an attribute came from an external query rather than a managed resource.

# practical example 1

Production Pattern: Dynamic Ubuntu AMI Lookup

ami.tfDYNAMIC AMI LOOKUP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
data "aws_ami" "ubuntu_latest" {
  most_recent = true
  owners      = ["099720109477"] # Canonical's Official AWS Account ID

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu_latest.id
  instance_type = "t3.micro"
}
security guardrail

Always Specify the "owners" Argument

Anyone can publish a public AMI to the AWS Marketplace with the name ubuntu-22.04. Specifying Canonical's verified AWS Account ID (099720109477) or Amazon Linux (amazon) ensures you never boot malicious third-party images.

automation

Auto Patching on Fresh Deployments

Because most_recent = true is set, new environments automatically inherit the latest security-patched kernel release from Canonical.

# practical example 2

Querying Pre-Existing VPCs & Subnets

network_lookup.tfVPC & SUBNETS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# 1. Fetch the default VPC or query by tag
data "aws_vpc" "selected" {
  default = true
}

# 2. Fetch all public subnet IDs inside that VPC
data "aws_subnets" "public" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.selected.id]
  }
}

# 3. Use the first subnet to launch an instance
resource "aws_instance" "web" {
  ami       = data.aws_ami.ubuntu_latest.id
  subnet_id = data.aws_subnets.public.ids[0]
}

Zero Hardcoded Network IDs

Notice that this entire configuration runs without a single hardcoded vpc-xxxxxxxx or subnet-xxxxxxxx string. You can execute this template in any AWS account and region, and it adapts dynamically.

# contextual lookups

Querying AWS Account ID & Current Region

context.tfCONTEXTUAL METADATA
1 2 3 4 5 6 7 8 9 10 11 12 13 14
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# Outputs contextual information
output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

output "region_name" {
  value = data.aws_region.current.name
}
IAM ARNs

Constructing Exact IAM ARNs

When generating IAM policies or S3 bucket policies that require an ARN like arn:aws:iam::123456789012:root, you can use data.aws_caller_identity.current.account_id rather than hardcoding the 12-digit account number.

# cheat sheet

Data Sources Quick Summary

RuleExplanation
Exact MatchingIf a data query returns 0 results or >1 result where 1 was expected (like aws_ami), Terraform will error during plan.
Read-Only SafetyRunning terraform destroy will never delete resources queried through a data block.
Use Filters JudiciouslyCombine multiple filters (e.g. tag + VPC ID) to ensure queries resolve to exact unambiguous targets.