How do you diagram an AWS VPC (subnets, NAT, and gateways) in Mermaid?
Diagram an AWS VPC in Mermaid architecture-beta: where public vs private subnets, the NAT gateway, Internet Gateway, and S3 gateway endpoints belong.
Model the VPC as a group nested inside a Region inside an AWS Account, then place public and private subnet groups inside it. Put the NAT gateway in a public subnet, attach one Internet Gateway to the VPC edge, and keep S3 in the Region reached by a gateway endpoint. For IPv6, use an egress-only Internet Gateway at the edge.
Almost every AWS architecture diagram contains a VPC, but almost none of them are really about the VPC. It shows up as a big box that a load balancer and a couple of instances happen to sit inside, drawn from memory, with a public and a private subnet that look identical except for the word on the label. That is fine until someone asks the questions the picture is supposed to answer: which subnet can actually reach the internet, where does outbound traffic from the database tier go, and why is the NAT gateway on this side rather than that one.
This post builds the VPC from the inside out and treats each element as something that encodes a networking fact, not decoration. If you have not set up architecture-beta with AWS icons yet, start with how to draw AWS architecture diagrams in Mermaid and the architecture-beta syntax reference for the grammar — groups, the in <group-id> nesting clause, and port-anchored edges like app:R --> L:nat. Here we assume that and go straight to the networking semantics.
A VPC diagram is a strict containment tree: AWS Account > Region > VPC > subnet > instance. The VPC, subnets, Region, and account are all group containers; the Internet Gateway attaches to the VPC edge (one per VPC), the NAT gateway lives in a public subnet, and a private instance reaches the internet only by routing to that NAT — never straight to the IGW. S3 sits in the Region, not the VPC, reached over a gateway endpoint attached to the VPC. Draw it that way and it validates at zero violations in LatixEngine.
What are the building blocks of an AWS VPC diagram, and how do they nest?
A correct VPC picture is defined less by which icons you pick than by what contains what. The containment hierarchy is not stylistic — each level is a real scoping boundary in AWS, and getting the nesting wrong is getting the architecture wrong.
The tree is always the same:
- AWS Account — the outermost group. Everything regional belongs to an account.
- Region — a group directly inside the account. A Region is a geographic container, never a single service node, and it holds VPCs and regional services.
- VPC — a group directly inside the Region. A VPC is a regional construct: it lives in exactly one Region and must contain at least one subnet to mean anything.
- Subnet (public or private) — a group directly inside the VPC. Subnets are subdivisions of the VPC's IP range and belong to exactly one VPC.
- Instance (EC2, and other subnet-scoped resources) — a service directly inside a subnet. An EC2 instance's network interface always lands in exactly one subnet, so on the diagram it sits directly inside a subnet, not floating under the VPC.
Two important resources sit beside the subnets rather than inside them: the Internet Gateway and the gateway VPC endpoint both attach to the VPC as a whole. And one resource you might expect inside the VPC — S3 — actually lives up at the Region level. Both of those are covered below.
These are the VPC-networking-specific slugs the rest of this post uses. For the broad service table (EC2, Lambda, RDS, and friends), see the how to draw AWS architecture diagrams in Mermaid lookup and the icon catalog.
| Element | Role in the diagram | Mermaid slug |
|---|---|---|
| AWS account | group (outermost) | aws:aws-account |
| Region | group, inside the account | aws:region |
| VPC | group, inside the Region | aws:virtual-private-cloud-vpc |
| Public subnet | group, inside the VPC | aws:public-subnet |
| Private subnet | group, inside the VPC | aws:private-subnet |
| Internet Gateway | service, attached to the VPC | aws:res-amazon-vpc-internet-gateway |
| NAT gateway | service, inside a public subnet | aws:res-amazon-vpc-nat-gateway |
| Gateway VPC endpoint | service, inside the VPC | aws:res-amazon-vpc-endpoints |
| Route table (VPC router) | service | aws:res-amazon-vpc-router |
EC2 instances use aws:arch-amazon-ec2 and S3 uses aws:arch-amazon-simple-storage-service; those are the two non-networking slugs the examples below reach for.
What actually makes a subnet public or private in a diagram?
This is the single most misunderstood thing about VPC diagrams, so it is worth being precise: there is no such thing as an intrinsically public or private subnet. A subnet is exactly as public as its route table says it is. A subnet is public when its route table has an entry sending 0.0.0.0/0 (and/or ::/0 for IPv6) to the Internet Gateway. A subnet is private when it has no such route. That is the whole distinction. The two subnet types even have identical everything else — same VPC, same kind of instances, same NACLs by default.
So in a diagram, "public vs private" should never be communicated by a colour or the word on the label alone. It is communicated by connectivity: which resources connect to the IGW directly (a public-subnet pattern) versus which route their egress through a NAT gateway (a private-subnet pattern). When you use the aws:public-subnet and aws:private-subnet group icons, the label matches the icon, but the edges are what make the claim true. If you draw an instance inside aws:private-subnet with a direct line to the IGW, you have drawn a contradiction — a "private" subnet with a public route — and LatixEngine flags it.
The route table itself is the mechanism, and AWS models it as the VPC router (slug aws:res-amazon-vpc-router). You usually do not draw a separate route-table node in a communication diagram — the routing is implied by the edges — but if you want to make the routing explicit, the router icon is the canonical representation. There is no separate "route table" glyph in the catalog; the VPC router is it.
What is the difference between an Internet Gateway and a NAT gateway, and how do you draw each?
They are near-opposites: an Internet Gateway is a VPC-level, two-way door to the internet; a NAT gateway is a subnet-level, outbound-only relay for private resources. Yet they are frequently drawn as two similar boxes near the top of the VPC, as if they were interchangeable.
| Internet Gateway | NAT gateway | |
|---|---|---|
| Scope | VPC-level attachment | Subnet-level resource |
| How many | At most one per VPC | One per public subnet you want to serve |
| Where it sits in the diagram | Attached to the VPC edge | Inside a public subnet |
| Direction of traffic | Two-way (inbound + outbound) for public-IP resources | Outbound-only, on behalf of private resources |
| Purpose | The VPC's door to the public internet | Lets private instances egress while staying unreachable |
Two placement rules fall straight out of that table. The Internet Gateway attaches to the VPC as a whole — it sits at the VPC edge, directly inside the VPC group, never inside a subnet — and a VPC has at most one IGW. It also has to actually connect to an internet resource to mean anything; an IGW wired to nothing is an incomplete diagram. The NAT gateway goes inside a public subnet, precisely because it needs that subnet's route to the IGW to do its job. A NAT gateway drawn in a private subnet cannot reach the IGW and is broken.
For IPv6, the outbound-only analog of a NAT gateway is the egress-only Internet Gateway. It plays the same role — outbound-only connectivity for private resources — but for IPv6 traffic, and like the IGW it attaches at the VPC level rather than living in a subnet. Treat this as a placement analogy only: the catalog has a single Internet Gateway icon and the validator enforces one IGW per VPC, so do not model an egress-only IGW by adding a second aws:res-amazon-vpc-internet-gateway alongside a real IGW. What matters here is the position — VPC edge, never inside a subnet.
How does a resource in a private subnet reach the internet?
Through the NAT gateway, in a specific three-hop chain — and this is the part diagrams get wrong most often. A resource in a private subnet has no public IP and, by definition, no route to the Internet Gateway. Its outbound internet traffic must egress like this:
private instance → NAT gateway (in a public subnet) → Internet Gateway (VPC edge) → internet.
The tempting shortcut — drawing a line straight from a private-subnet EC2 to the IGW — depicts a path that physically cannot exist, and it is exactly the kind of thing a reviewer's eye slides right over. Here is the egress path on its own, as a minimal but complete and valid diagram:
architecture-beta
service internet(aws:res-internet-light)[Internet]
group account(aws:aws-account)[AWS Account]
group region(aws:region)[us-east-1] in account
group vpc(aws:virtual-private-cloud-vpc)[VPC] in region
service igw(aws:res-amazon-vpc-internet-gateway)[Internet Gateway] in vpc
group pub(aws:public-subnet)[Public Subnet] in vpc
service nat(aws:res-amazon-vpc-nat-gateway)[NAT Gateway] in pub
group priv(aws:private-subnet)[Private Subnet] in vpc
service app(aws:arch-amazon-ec2)[App Server EC2] in priv
app:R --> L:nat
nat:T --> B:igw
igw:T --> B:internetNotice what is not there: no edge from app to igw. The only thing touching the IGW from below is the NAT gateway. A public-subnet instance, by contrast, is allowed to reach the IGW directly, because it has a public IP and the public subnet's IGW route — that is the whole point of a public subnet. The "why it breaks" catalogue for the private-EC2-to-IGW mistake, the NAT-in-a-private-subnet mistake, and the public-database mistake lives in common AWS architecture mistakes; this post just shows the shape that is correct.
Where do S3 and gateway VPC endpoints belong in the picture?
S3 is the classic "drawn in the wrong container" resource. Engineers put an S3 bucket inside the VPC because their instances talk to it — but S3 is a regional service that lives at the Region level, not inside any VPC. A bucket is scoped to a Region, so on the diagram it is a direct child of the Region group, a sibling of the VPC, never inside it. The common AWS architecture mistakes post shows the anti-pattern version — a bucket boxed inside the VPC — if you want to see the wrong shape beside this correct one.
So how does a private instance reach S3 without routing all the way out through the NAT and IGW? A gateway VPC endpoint. A gateway endpoint injects an AWS-managed prefix-list route into the VPC's route tables, keeping S3 (and DynamoDB) traffic on the AWS private network instead of the public internet. Because it is a route-table target rather than something that lives in one subnet, the gateway endpoint attaches directly to the VPC — not inside a subnet — and it exists only to reach S3 or DynamoDB. It must also be in the same Region as the bucket it serves: a gateway endpoint is only available in the Region where it was created, so the endpoint's VPC and the bucket have to share a Region. (Cross-Region access, or reaching services other than S3/DynamoDB, needs an interface endpoint — a different construct that is out of scope here.)
Put together, the S3 access path is: private instance → gateway endpoint (in the VPC) → S3 (in the same Region). No NAT, no IGW, no public internet.
What does a complete, validated VPC diagram look like?
Here is the full skeleton with everything in its correct place: an account holding a Region; the Region holding both the VPC and the S3 bucket; one Internet Gateway on the VPC edge; a gateway endpoint on the VPC; a public subnet with a web server and the NAT gateway; and a private subnet with an app tier and a database tier. It validates at zero rule violations in LatixEngine.
architecture-beta
service internet(aws:res-internet-light)[Internet]
group account(aws:aws-account)[AWS Account]
group region(aws:region)[us-east-1] in account
service s3(aws:arch-amazon-simple-storage-service)[Assets Bucket S3] in region
group vpc(aws:virtual-private-cloud-vpc)[VPC 10.0.0.0/16] in region
service igw(aws:res-amazon-vpc-internet-gateway)[Internet Gateway] in vpc
service gwe(aws:res-amazon-vpc-endpoints)[S3 Gateway Endpoint] in vpc
group pub(aws:public-subnet)[Public Subnet 10.0.1.0/24] in vpc
service web(aws:arch-amazon-ec2)[Web Server EC2] in pub
service nat(aws:res-amazon-vpc-nat-gateway)[NAT Gateway] in pub
group priv(aws:private-subnet)[Private Subnet 10.0.11.0/24] in vpc
service app(aws:arch-amazon-ec2)[App Server EC2] in priv
service db(aws:arch-amazon-ec2)[Database EC2] in priv
igw:T --> B:internet
web:R --> L:igw
nat:T --> B:igw
app:R --> L:nat
app:B --> T:gwe
gwe:B --> T:s3
app:L --> R:dbEvery edge here is a networking claim you could defend in a review: the web server (public) reaches the IGW directly; the NAT gateway (public) reaches the IGW; the app server (private) egresses through the NAT, never the IGW; the app server reaches S3 over the gateway endpoint; and S3 sits in the Region, not the VPC.
Multi-AZ is a small extension of the same idea (the base duplication pattern is walked through in how to draw AWS architecture diagrams in Mermaid). There is no availability-zone group icon, so you represent AZs by duplicating the subnet groups and labeling each with its zone — the standard pattern being one public and one private subnet per AZ, with an independent NAT gateway in each AZ's public subnet so a zone failure does not take out egress for the whole VPC:
architecture-beta
service internet(aws:res-internet-light)[Internet]
group account(aws:aws-account)[AWS Account]
group region(aws:region)[us-east-1] in account
group vpc(aws:virtual-private-cloud-vpc)[VPC] in region
service igw(aws:res-amazon-vpc-internet-gateway)[Internet Gateway] in vpc
group puba(aws:public-subnet)[Public Subnet AZ-a] in vpc
service nata(aws:res-amazon-vpc-nat-gateway)[NAT Gateway a] in puba
group pubb(aws:public-subnet)[Public Subnet AZ-b] in vpc
service natb(aws:res-amazon-vpc-nat-gateway)[NAT Gateway b] in pubb
group priva(aws:private-subnet)[Private Subnet AZ-a] in vpc
service appa(aws:arch-amazon-ec2)[App a] in priva
group privb(aws:private-subnet)[Private Subnet AZ-b] in vpc
service appb(aws:arch-amazon-ec2)[App b] in privb
igw:T --> B:internet
nata:T --> L:igw
natb:T --> R:igw
appa:R --> L:nata
appb:R --> L:natbStill one IGW for the whole VPC (that limit does not change with AZ count), but a NAT per AZ so each zone's private subnet has a local egress path.
How do you keep a VPC diagram correct as it grows?
The failure mode of every hand-drawn VPC diagram is subtle wrongness: a database quietly in a public subnet, a NAT that drifted into the wrong subnet during an edit, a private instance with a stray line to the IGW. These are precisely the mistakes an author cannot see in their own diagram and a reviewer skims past, because the picture looks plausible.
That is the gap the validation closes. Paste any of these diagrams into the LatixEngine editor and it checks two things: that every aws: slug is real (against the icon catalog), and that the placement obeys AWS's networking rules — NAT in a public subnet, one IGW per VPC, private instances egressing via NAT not IGW, S3 in the Region, the gateway endpoint in the same Region as its bucket. Wrong placement gets flagged with the reason, so the mistake surfaces at the cheapest possible moment: before the diagram ships. You do not have to argue about it; validate it in the LatixEngine editor and the diagram tells you.
Once the network fabric is right, the natural next steps:
- Drop a real workload into this skeleton — a three-tier app, a container service, an event pipeline — from the AWS architecture patterns gallery.
- Generate the VPC diagram from your infrastructure instead of hand-drawing it, using turn your Terraform into a Mermaid diagram.
- Publish the finished diagram where your team reads it with render and embed it in GitHub, Notion, or Confluence.
The VPC is the one part of an AWS diagram where "looks about right" and "is actually right" diverge most, because the correctness lives in routing you cannot see. Model it as a strict containment tree, let the edges — not the labels — carry the public/private distinction, and let the validator confirm it. LatixEngine is free, runs entirely in the browser with no login or install, so there is no reason to ship a VPC diagram you have not checked.
Frequently asked questions
What is the difference between a public and private subnet in a VPC diagram?
The only real difference is the route table. A public subnet has a route sending 0.0.0.0/0 to the Internet Gateway; a private subnet does not. In the diagram this is not a color or a label — it is expressed by which resources connect to the IGW (directly, for public) versus a NAT gateway (for private). Both are drawn as group containers directly inside the VPC.
Where does the NAT gateway go in a VPC diagram — the public or private subnet?
The NAT gateway always lives in a public subnet, never a private one. It needs the public subnet's route to the Internet Gateway to forward outbound traffic. It serves the private subnets: a private instance sends egress traffic to the NAT gateway, which sits in the public subnet and relays it to the IGW. Placing the NAT in a private subnet is a common mistake that breaks egress entirely.
What is the difference between an Internet Gateway and a NAT gateway?
They are opposite-purpose objects, not interchangeable boxes. An Internet Gateway is a VPC-level attachment (one per VPC) that provides two-way connectivity for resources with public IPs. A NAT gateway is a subnet-level resource that provides outbound-only connectivity for private resources, hiding them behind a single address. In a diagram the IGW attaches to the VPC edge, while the NAT sits inside a public subnet.
Does an S3 bucket go inside the VPC in an architecture diagram?
No — S3 is a Region-level service that sits beside the VPC, not inside it. A bucket is scoped to a Region, so on the diagram it is a direct child of the Region group and a sibling of the VPC. A private instance reaches it over a gateway VPC endpoint that attaches to the VPC and keeps the traffic on the AWS private network — no NAT or Internet Gateway involved.
What AWS icon slugs do you use for VPC, subnet, NAT gateway, and Internet Gateway in Mermaid?
Use aws:virtual-private-cloud-vpc for the VPC group, aws:public-subnet and aws:private-subnet for the subnet groups, aws:res-amazon-vpc-nat-gateway for the NAT gateway, and aws:res-amazon-vpc-internet-gateway for the Internet Gateway. The Region and account containers are aws:region and aws:aws-account. These are all group or resource (res-) icons.
Can Mermaid architecture-beta draw a VPC with subnets and availability zones?
Yes. A VPC is a group, subnets are nested groups, and instances are services inside them, which is exactly what architecture-beta's group nesting is for. There is no dedicated availability-zone icon, so you represent multi-AZ by duplicating the subnet groups and labeling each with its AZ — typically one public and one private subnet per AZ, each public subnet holding its own NAT gateway.
Paste any example in this post into the LatixEngine editor to render it with native cloud icons and validate it against AWS, Azure, and GCP best practices. No login, no install.
Open the editor →