Why an inventory comes before any Terraform
Most teams that decide to adopt infrastructure as code start by writing Terraform for the first resource they can think of. A few weeks later they have a repository covering a fraction of the account and an uneasy feeling that nobody knows what the rest is. The missing step is a map.
An inventory answers four questions for every resource: what is it, who owns it, what depends on it, and what happens if it breaks. Without that, ordering the work is guesswork, and the riskiest items tend to be left until last, then avoided indefinitely. This deliverable is the discipline behind the ClickOps and cloud setup service, and it sits alongside the practical walkthrough in our blog post on moving from ClickOps to Terraform on AWS.
What a good inventory contains
A spreadsheet is enough if it is complete and maintained. We record:
- Identity: account, region, service, name, ARN or ID.
- Ownership: a person or team, and the environment (production, staging, sandbox, unknown).
- Cost: what it adds to the bill, taken from billing data rather than estimated.
- Dependencies: what talks to it and what it talks to.
- Risk: customer impact if it fails, and whether it holds data.
- Disposition: import, rebuild, replace, or delete.
- State of documentation: whether anyone can explain why it exists.
The last column matters more than it looks. Resources nobody can explain are often the most valuable to understand, because they were built during an incident or a deadline and then became load-bearing.
Finding what is really there
No single source is reliable. We cross-check several:
| Source | Good at | Blind spot |
|---|---|---|
| AWS Config or Resource Explorer | Listing resources across regions | Context, intent, ownership |
| Cost Explorer and billing data | Revealing forgotten, expensive items | Free or cheap resources |
| CloudTrail history | Who created what, and when | Anything older than retained logs |
| Interviews with the team | Why things exist | Memory is imperfect |
| Existing scripts and wikis | Partial intent | Often out of date |
Regions deserve special attention. Resources are often created in a region nobody uses by accident, and a security review is incomplete without them. Our organisation guardrails later prevent this by denying unused regions.
Deciding what happens to each resource
Not everything should be imported. The disposition decision is where judgement matters.
Import
Best for stateful or hard-to-replace resources that are well understood: VPCs, databases, buckets, IAM roles. Importing leaves the real resource untouched and attaches it to code.
Rebuild
Best for stateless or simple resources, or those whose current configuration is wrong. It is often cleaner to define a new load balancer properly and move traffic than to import a badly configured one and carry its mistakes forward.
Replace
Sometimes the right answer is not to codify a hand-built thing at all. A self-managed server running a queue might be replaced by a managed queue service. That belongs in a separate project, but the inventory should flag it.
Delete
Orphaned snapshots, unattached volumes, idle test environments and old load balancers can usually go, after a quiet period. Stop or isolate first, wait, then remove.
Importing safely
Terraform supports import blocks, which let you review an import as part of a normal plan:
import {
to = aws_s3_bucket.assets
id = "example-company-assets"
}
resource "aws_s3_bucket" "assets" {
bucket = "example-company-assets"
}
terraform plan -generate-config-out=generated.tf
terraform plan
The first command can draft configuration, which we then clean up. The aim is a plan that reports no changes. If a plan wants to replace a database or recreate a subnet, we stop and investigate before applying anything. Setting prevent_destroy on stateful resources gives a safety net while the code is still being corrected.
State should live remotely with locking from the start, as described in our Terraform landing zone code work, and imports should happen through pull requests so that a colleague sees what is being brought under management.
Ordering the waves
We usually sequence by risk and learning value:
- Low-risk, high-learning: buckets, DNS records, tags, IAM roles. The team gets used to the workflow.
- Foundations: networking, accounts and baseline security. See the landing zone work.
- Shared services: logging, monitoring, container registries.
- Stateful workloads: databases and queues, with backups verified first.
- Application compute: instances, clusters, autoscaling groups, last because they change most often.
Production data stores are not first, even though they are the scariest, because mistakes there are the most expensive.
Common failure modes
- Boil the ocean. Trying to import everything at once stalls the project. Waves keep momentum.
- Drift returns. If people can still click in the console, they will, and the code goes stale. Reducing write access is part of the plan.
- Imported mistakes. Faithfully codifying an insecure configuration just makes it permanent.
- One giant state file. It slows every plan and makes every change risky. We split state by environment and domain.
- No owner. An inventory with no one responsible for keeping it current is out of date within months.
When not to migrate
A throwaway prototype, a short-lived proof of concept or an environment about to be decommissioned is not worth codifying. And if your biggest risk is not infrastructure drift but missing backups, fix that first. The plan states this honestly.
Delivery and handover
You receive the inventory, the disposition decisions with reasoning, the wave plan, and the Terraform for completed waves in your repository. We document how to import a new resource, how to detect drift, and who approves changes. For ongoing support afterwards, see managed DevOps, or talk to us about scoping the first wave.
How to tell it is working
terraform planon managed stacks shows no unexpected changes.- New infrastructure is only ever created through pull requests.
- The inventory shrinks as items are imported, rebuilt or deleted.
- Nobody is afraid to touch a resource because they cannot tell what depends on it.