See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
The nesting order, and the labels each level carries
Region contains VPC contains availability zone contains subnet contains resource. Every level carries one piece of text a reader will look for. The VPC carries its CIDR block. Each availability zone carries its real name, eu-west-1a rather than AZ 1. Each subnet carries its tier (public, private, isolated), its CIDR, and its default route target.
Three tiers is the shape most production VPCs converge on: public subnets for anything with a route to an internet gateway, private subnets for compute that egresses through NAT, and isolated data subnets with no default route at all. Showing that a database subnet has no route to 0.0.0.0/0 is a stronger security statement than any padlock icon.
| Subnet | AZ | CIDR | Default route | | --- | --- | --- | --- | | public-a | eu-west-1a | 10.0.0.0/24 | internet gateway | | private-app-a | eu-west-1a | 10.0.16.0/20 | nat-a | | data-a | eu-west-1a | 10.0.64.0/22 | none | | public-b | eu-west-1b | 10.0.1.0/24 | internet gateway | | private-app-b | eu-west-1b | 10.0.32.0/20 | nat-b | | data-b | eu-west-1b | 10.0.68.0/22 | none |
Where each network component actually sits
The internet gateway attaches to the VPC, so draw it on the VPC edge, not inside a subnet. A NAT gateway does live in a subnet, specifically a public one, and production designs usually run one per availability zone so that a zone failure does not take egress with it. Drawing a single shared NAT is fine when that is the truth, and worth annotating, because it is both a cost decision and a failure domain.
Load balancers span zones. An ALB or NLB registers subnets in several availability zones, so it belongs at the VPC level or drawn across the zone containers. VPC endpoints split by type: gateway endpoints for S3 and DynamoDB attach to route tables and are best shown as a route entry, while interface endpoints are elastic network interfaces that really do sit inside a subnet and deserve a node when they matter.
Transit Gateway and peering connections attach at the VPC edge. If the VPC is a spoke on a Transit Gateway owned by a network account, draw the attachment and let the hub live in the multi-account view rather than duplicating the whole hub here.
Routes and rules without adding boxes
Route tables are configuration, not topology. Put the default route on the subnet container as a label (0.0.0.0/0 to nat-a) and only promote a route table to its own node when the routing itself is the subject of the diagram, for example an inspection VPC where traffic is deliberately hair-pinned through a firewall.
Security group rules work the same way: write the rule on the edge it permits. An arrow labelled tcp/5432 from the app security group carries the rule, the direction, and the reason. Network ACLs are worth drawing only when they carry a rule that is not implied by the security groups, which is rare and therefore notable.
Generating the VPC picture from Terraform
Almost every VPC in production was written as code, usually as a module with for_each over availability zones. Datadef parses the .tf and .tfvars files of a connected repository directly, so the subnet plan in the diagram comes from the declaration rather than from someone reading the console. No terraform init, no state file, no AWS credentials, read-only repository access.
Because the parse understands count and for_each, a subnet created once per zone stays one node carrying its real multiplicity instead of exploding into six identical boxes. A literal for_each map is counted directly and the node reads ×3. A for_each over a variable is followed to the call site and then into the tfvars, so it reads "per subnets (2)" when every environment agrees and "per subnets (DEV 2, PROD 3)" when they do not.
Nesting is derived rather than guessed. The catalog records, per resource type, which attribute points at the container the resource lives in: vpc_id for aws_subnet and aws_security_group, subnet_id for aws_nat_gateway and aws_instance. A reference through one of those attributes becomes containment, drawn as nesting and never as an arrow, which is what stops a VPC diagram turning into a mesh of id references. aws_vpc and aws_subnet also carry cidr_block onto the node, so the table above comes out of the .tf files rather than out of the console.
Registry modules such as terraform-aws-modules/vpc/aws cannot be expanded from your repository alone. The call becomes a single node labelled with its source and its pinned version, standing for everything it provisions and wired to whatever references its outputs. In the module reference table its Resources cell reads "external", which is the honest answer rather than a count nobody could compute.
Keep the picture attached to the code
FAQ
Should a load balancer be drawn inside an availability zone?
How do I show public versus private subnets clearly?
Do VPC endpoints belong in the diagram?
Should CIDR blocks appear on the diagram?
Can a VPC diagram be generated from Terraform code?