Suppose someone creates a database through the AWS console while investigating a production issue. They need somewhere to restore a backup, test a query, and work out what went wrong.
The issue gets fixed. The incident channel goes quiet. The database stays.
The next morning, the deployment pipeline runs. Terraform reports “No changes.” Should that result reassure anyone about the extra database?
It depends on what they think Terraform just checked.
What the plan actually covers
Terraform uses configuration and state to work out which infrastructure changes to propose. State links resource instances in your configuration to objects in the cloud. A normal plan refreshes its view of tracked resources and compares that information with the configuration.
HashiCorp explains that relationship in its documentation on Terraform state. A database created separately through the console, with no connection to that configuration or state, does not automatically become part of the process.
The plan can finish successfully while that database keeps running.
How much ground does your plan cover?
“We manage everything with Terraform” is a useful statement to examine closely. Does everything mean production? Every environment? Every account and subscription? Does it include the infrastructure that existed before the current team arrived?
There might be several repositories and workspaces, shared infrastructure owned by another team, or resources created by an application through provider APIs.
Running a plan in one workspace tells you about that workspace’s proposed changes. Terraform’s planning documentation describes what the command evaluates and reports. That scope can get lost when a successful pipeline becomes shorthand for the health of the whole account.
Follow the database from plan to inventory
Let’s review RDS database instances in one AWS account and region. Use an already initialized Terraform directory and an AWS CLI profile named production with permission to describe RDS instances. Substitute your own profile, region, and resource addresses. All outputs below are illustrative, with fictional identifiers; longer output is shortened.
1. Confirm where you are looking
terraform workspace show
aws sts get-caller-identity \
--profile production \
--query Account --output textproduction
111122223333Check this account against your Terraform provider configuration. A workspace named production does not guarantee a particular AWS account. Provider aliases and role assumptions can point somewhere different from the CLI profile.
The STS caller identity reference explains the account and identity returned by this command.
2. Check the plan and its resource addresses
terraform plan
terraform state listNo changes. Your infrastructure matches the configuration.
module.database.aws_db_instance.main
module.network.aws_security_group.databaseThe plan proposes no changes. The state lists one managed database instance and a security group. Those are Terraform addresses, so we still need to connect them to actual AWS identifiers. The state list reference covers listing and filtering resources, including those inside modules.
3. Ask AWS which instances exist
Now inspect the same account in Ireland. The query keeps just the instance identifier and ARN, giving us something concrete to compare:
aws rds describe-db-instances \
--profile production \
--region eu-west-1 \
--query 'DBInstances[].{Name:DBInstanceIdentifier,ARN:DBInstanceArn}' \
--output json[
{
"Name": "app-production",
"ARN": "arn:aws:rds:eu-west-1:111122223333:db:app-production"
},
{
"Name": "incident-restore",
"ARN": "arn:aws:rds:eu-west-1:111122223333:db:incident-restore"
}
]AWS returns two instances. We have found the restore, but a difference in counts alone does not establish ownership. Other workspaces may cover it. The RDS CLI reference documents the response fields and pagination. This query covers database instances in one region; clusters and other resource types need separate checks.
4. Match the ARN before drawing a conclusion
terraform state show 'module.database.aws_db_instance.main'# module.database.aws_db_instance.main:
resource "aws_db_instance" "main" {
arn = "arn:aws:rds:eu-west-1:111122223333:db:app-production"
identifier = "app-production"
# Other attributes omitted.
}The ARN matches the application database. In this example, the restored instance has no corresponding managed database resource in this state, which explains why the clean plan told us nothing about it. The state show reference describes how to inspect an individual resource. Treat any real state output as internal infrastructure information.
Repeat the identity check across the relevant workspaces and deployment tools before calling the restore unmanaged. These commands inspect information without changing cloud resources. Collection permissions still determine what you can discover.
Give each unexplained resource a next step
Compare resource identifiers rather than relying on similar names or matching counts. For anything you cannot connect to a known management process, work through a few questions:
- Who owns it, and what is it used for?
- Is it managed in another workspace, repository, or tool?
- Was it created automatically by a service that still needs it?
- If it is intentionally managed manually, where is that decision recorded?
- Does it need importing, documentation, further investigation, or removal?
The restore might belong to another team, need importing into the intended configuration, or be ready for removal. Record the owner and next action so the same question does not come back at the next review.
Be careful about turning an unexplained resource into an immediate cleanup task. Confirm dependencies and ownership before changing it. Being absent from the workspace you checked says little about whether something is safe to remove.
Make the scope visible
The useful outcome is a record that another engineer can follow. If a resource belongs to a different team, record where it is managed. If something is intentionally maintained outside Terraform, document that decision. If an incident requires temporary infrastructure, give someone responsibility for reviewing it afterwards.
You can apply the same exercise to Azure subscriptions and other resource types. Keep the scope clear, account for gaps in collection permissions, and avoid assuming that a single inventory source sees everything.
Cloustral helps with discovery by collecting AWS and Azure inventory and presenting resources and architecture views. That gives teams a starting point for investigating what exists and how it fits together. You still need to connect those resources to the processes and people responsible for them.
The next time a plan reports “No changes,” ask whether everyone reading that result understands which part of the environment it covers.