DEPENDENCIES → STACKIT · ARCHITECTURE

How to build a migration dependency graph

Start with the references your Terraform already states, let them fix the order of the target, then add by hand the links that live in application configuration. StackToIT reads the references between resources as edges, sorts the units into waves so that nothing moves before what it depends on, and says where the graph is blind. This page shows that on a web shop with eleven units, including one explicit depends_on that moves three units by two waves.

Three edge types, one of them explicit

A small estate written for this page and assessed by StackToIT on 4 November 2026. Left: the web server, its elastic IP and the DNS record of the input (lines 83 to 105; the comment sits in the input file). Right: the four edges of the graph in assessment.json that start at the server and at the DNS record, re-indented for reading.

Example inputmain.tf
resource "aws_instance" "web" {
  ami                    = "ami-0abcdef1234567890"
  instance_type          = "m6i.large"
  subnet_id              = aws_subnet.web.id
  vpc_security_group_ids = [aws_security_group.web.id]

  # Nothing in the arguments refers to the database or the bucket:
  # the application reads both from its configuration at run time.
  depends_on = [aws_db_instance.shop]
}

resource "aws_eip" "web" {
  instance = aws_instance.web.id
  domain   = "vpc"
}

resource "aws_route53_record" "www" {
  zone_id = "Z0123456789EXAMPLE"
  name    = "www.shop.example.com"
  type    = "A"
  ttl     = 300
  records = [aws_eip.web.public_ip]
}
Graph edgesassessment.json
[
  {
    "from": "aws_instance.web",
    "to": "aws_db_instance.shop",
    "type": "DEPENDS_ON",
    "via": "depends_on",
    "explicit": true
  },
  {
    "from": "aws_instance.web",
    "to": "aws_security_group.web",
    "type": "ATTACHED_TO",
    "via": "vpc_security_group_ids"
  },
  {
    "from": "aws_instance.web",
    "to": "aws_subnet.web",
    "type": "DEPLOYED_IN",
    "via": "subnet_id"
  },
  {
    "from": "aws_route53_record.www",
    "to": "aws_eip.web",
    "type": "DEPENDS_ON",
    "via": "records"
  }
]
  • Three edge types

    DEPLOYED_IN says where a resource lives (a subnet, a VPC). ATTACHED_TO says what it carries (a security group). DEPENDS_ON says it needs another resource to exist first. Each edge names the argument it was read from.

  • Explicit beats silent

    The server refers to the database in no argument. The edge to the database exists only because depends_on says so, and the edge carries the flag explicit.

  • The record follows the address

    The DNS record reads the public address of the elastic IP, so it sits behind it, which sits behind the server.

What the explicit link does to the waves

Two runs of the same estate, one with the depends_on line and one without. Waves are computed from the dependencies: a unit moves only after everything it depends on, and units in one wave can move in parallel.

UnitWave with depends_onWave without
aws_s3_bucket.media11
aws_vpc.shop11
aws_security_group.web22
aws_subnet.data_a22
aws_subnet.data_b22
aws_subnet.web22
aws_security_group.db33
aws_db_instance.shop44
aws_instance.web53
aws_eip.web64
aws_route53_record.www75

The summary counts 14 dependencies between units with the line and 13 without. Without it the server shares wave 3 with the database security group and no longer waits for the database. The graph itself lists 15 edges for the first run because the database subnet group is part of the database unit, not a unit of its own.

Where each edge type comes from

Edge typeTerraform argument that makes itSeen in this run
DEPLOYED_INvpc_id, subnet_id, subnet_ids, db_subnet_group_nameSubnets and security groups in the VPC; the server in its subnet; the database in its subnet group (9 edges)
ATTACHED_TOvpc_security_group_ids, ingress.security_groupsBoth security groups on their resources; the database group admitting the web group (3 edges)
DEPENDS_ONinstance, records, depends_onElastic IP on the server, record on the elastic IP, server on the database (3 edges)

Counts are from the first run. Which arguments count as references is decided per resource type by the engine.

The place the graph is blind

Same run. The DNS record names its zone with a literal identifier, so there is no reference to follow. Left: the record in the input. Right: the diagnostic the run wrote for it (its provenance field left out).

Example inputmain.tf
resource "aws_route53_record" "www" {
  zone_id = "Z0123456789EXAMPLE"
  name    = "www.shop.example.com"
  type    = "A"
  ttl     = 300
  records = [aws_eip.web.public_ip]
}
Graph diagnosticassessment.json
[
  {
    "severity": "warning",
    "message": "could not find the parent of aws_route53_record.www through attribute zone_id; assessed on its own",
    "node_id": "aws_route53_record.www"
  }
]
  • Assessed on its own

    The record is still classified, and its address change is listed as a cutover step. What is missing is its link to the zone, which the plan asks you to bind to a STACKIT DNS zone by variable.

  • The explicit link reaches the cutover step

    With the depends_on line, the step that switches the database connection names the web server as a client: the source references the database from aws_instance.web. In the run without that line it says that no resource of the source references the database, that the applications are outside the source and that their owner lists them. The media bucket gets the second wording in both runs.

Building the graph, step by step

  1. 1. Run on the Terraform you have

    Let the engine read the references. The result is the graph, the waves in migration/waves.csv and migration/technical-sequencing.md, and a drawing in architecture/dependencies.mmd.

  2. 2. Read the diagnostics

    Open the graph diagnostics in assessment.json. A literal identifier, a data source or a value the run could not resolve means a missing edge; note each one.

  3. 3. Confirm the application groups

    The run proposes groups from references and ingress rules, here one group of five units. The proposal changes nothing until you set review: confirmed in applications-template.yaml and pass the file back with --applications.

  4. 4. Add the runtime links

    Ask each application owner which databases, buckets, queues and APIs the application reads from configuration. In this run the media bucket belongs to no group and sits in wave 1: the Terraform links it to nothing, so whether the shop reads it is a question only its owner can answer.

  5. 5. Re-run and compare

    After adding explicit links, run again and compare waves.csv. A link that moves nothing is either redundant or on the wrong unit.

  6. 6. Turn waves into a plan

    The waves are a starting point for the architect's wave plan, not a schedule. Add data sizes, freeze windows and the cutover order on top.

Questions about dependency mapping

Does StackToIT discover dependencies from running systems?

No. It reads Terraform, OpenTofu and RVTools input. Dependencies that exist only at run time, such as a connection string in a configuration file, are not in the graph; you add them through the application groups and the decisions.

What does an explicit depends_on change besides the waves?

In this run it also changed the cutover plan. With the line, the database connection step names the web server as a known client; without it, the step says the applications are outside the source. A depends_on that mirrors a real runtime link therefore documents a client in the plan, not only an order.

Are waves the order of the cutover?

No. They are the order in which units can be built or moved because of their dependencies. The cutover facts, such as DNS and address re-binding, are listed separately in technical-sequencing.md.

Why does a unit with no references land in wave 1?

It has nothing to wait for. The media bucket is in wave 1 with the VPC. That says the Terraform links it to nothing, not that nothing uses it.

Sources

Your next move: STACKIT.

Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.