In the AWS console a VPC looks like one thing you click "Create" on. From the SDK it is nothing of the sort. A VPC is a small collection of separate resources - each with its own id, its own create call, and its own tear-down call - that you wire together by hand.
This page maps that collection. Once you can name every object and the id that links it to the next, the rest of this section (subnets, routing, NAT, peering, flow logs) is just detail.
A VPC is not a monolith. It is a CIDR block plus a graph of smaller resources - subnets, route tables, gateways, security groups - that you connect by id.
Insight: the console's "Create VPC" wizard runs a dozen SDK calls for you. The SDK makes each one explicit, which is why VPC automation feels verbose at first.
When to Use: any time you build networking from code (Terraform, CloudFormation, or raw SDK) and need a mental model of what each resource is and how it links to the next.
Limitations/Trade-offs: the SDK gives you full control at the cost of doing every wiring step yourself; forget one association and traffic silently fails.
Related Topics: creating a VPC end to end, subnet routing, security groups vs NACLs, and flow logs.
A VPC is defined by a CIDR block - a private IPv4 range like 10.0.0.0/16 that reserves 65,536 addresses for your network. Everything else lives inside that range. You create it with CreateVpc, and you get back a VpcId (vpc-...) that every other resource references.
A subnet carves a smaller CIDR (say 10.0.1.0/24) out of the VPC range and pins it to a single Availability Zone. A subnet cannot span AZs, so a highly available design uses at least two. CreateSubnet returns a SubnetId and takes the parent VpcId - that reference is the first piece of wiring.
A route table is a list of rules that says "for this destination CIDR, send traffic to this target." Every subnet is associated with exactly one route table. The main route table is the default; you create your own with CreateRouteTable and attach it with AssociateRouteTable.
An internet gateway is the VPC's door to the public internet. You create it with CreateInternetGateway and bolt it onto the VPC with AttachInternetGateway. On its own it does nothing - a subnet only reaches it if a route table points 0.0.0.0/0 at the gateway's id.
A security group is a stateful firewall that attaches to network interfaces (and therefore to instances). A network ACL is a stateless firewall that attaches to subnets. Both filter traffic, but they sit at different layers and behave differently - covered in depth on its own page.
The objects only matter because of how they reference each other. Think of the whole VPC as a graph of ids.
A subnet holds a VpcId - it belongs to one VPC.
A route-table association holds both a RouteTableId and a SubnetId - that record is what routes a subnet.
A route inside the table holds a destination CIDR and a target id (GatewayId, NatGatewayId, VpcPeeringConnectionId, and so on).
An internet gateway attachment holds an InternetGatewayId and a VpcId.
An instance's network interface (ENI) holds a SubnetId and a list of GroupIds (security groups).
The single most important relationship is the route-table association. A subnet is "public" only because the route table associated with it has a 0.0.0.0/0 route to an internet gateway. There is no public: true flag. Publicness is an emergent property of routing.
That is why the same subnet definition can be public or private depending entirely on which route table it is associated with. Move the association to a table whose default route points at a NAT gateway instead, and the subnet becomes private with outbound-only access.
Security groups reference each other too. A rule's source can be a CIDR or another security group id, which is how you say "the web tier may talk to the database tier" without naming any IP address.
Default vs custom. Every account ships with a default VPC per region, a default subnet per AZ, a main route table, and a default security group. It is fine for experiments, but production networks are almost always custom VPCs so you control the CIDR plan and routing explicitly.
DNS attributes are off-ish by default. A freshly created custom VPC has enableDnsSupport on but enableDnsHostnames off, so instances do not get public DNS names until you flip it with ModifyVpcAttribute. This trips up a lot of first VPCs.
IPv6 is a parallel stack. Everything above is IPv4. A VPC can also carry an IPv6 CIDR, with its own routes (::/0), and an egress-only internet gateway plays the NAT-gateway role for IPv6 outbound. The IPv4 and IPv6 route entries live in the same route table but are entirely separate rules.
Ordering is real. You cannot attach an internet gateway before the VPC exists, cannot add a 0.0.0.0/0 -> igw route before the gateway is attached, and cannot delete a VPC while any subnet, gateway, or ENI still references it. The dependency graph dictates both create order and the reverse tear-down order.
Why the SDK feels verbose. The console wizard hides all of this: one click fans out to CreateVpc, CreateSubnet (times N), CreateInternetGateway, AttachInternetGateway, CreateRouteTable, CreateRoute, and AssociateRouteTable. Infrastructure-as-code tools do the same fan-out declaratively. Understanding the raw calls is what lets you debug when a declarative tool leaves traffic mysteriously black-holed.
"A VPC is a single resource I create in one call." - No. CreateVpc makes only the CIDR container; subnets, gateways, and routes are separate resources and separate calls.
"Subnets have a public flag." - No. A subnet is public only because its route table routes 0.0.0.0/0 to an internet gateway. Change the association and it changes.
"An internet gateway gives instances internet access." - Not by itself. Without a route pointing at it and a public IP on the instance, an attached IGW does nothing.
"Security groups and NACLs are the same firewall twice." - No. Security groups are stateful and attach to ENIs; NACLs are stateless and attach to subnets. They filter at different layers.
"New instances get DNS names automatically." - Only if enableDnsHostnames is set on the VPC, which is off by default on custom VPCs.
What is the smallest set of objects a working public VPC needs?
A VPC (CIDR), at least one subnet, an internet gateway attached to the VPC, a route table with a 0.0.0.0/0 route to that gateway, and an association linking the subnet to that route table.
Why does creating a VPC in the SDK take so many calls?
Because each piece - VPC, subnet, gateway, route table, route, association - is a separate resource with its own create call. The console wizard hides this fan-out behind one button.
What actually makes a subnet public?
Its associated route table having a 0.0.0.0/0 route to an internet gateway (plus instances getting a public IP). There is no public/private attribute on the subnet itself.
Can a subnet span two Availability Zones?
No. A subnet lives in exactly one AZ. To be highly available you create one subnet per AZ and spread resources across them.
What is the difference between a security group and a network ACL?
A security group is a stateful firewall attached to network interfaces; return traffic is allowed automatically. A network ACL is a stateless firewall attached to subnets; you must allow both directions explicitly.
What does a route table association do?
It links a subnet to a route table, deciding how that subnet's traffic is routed. Each subnet has exactly one association; unassociated subnets fall back to the VPC's main route table.
Do I need to handle IPv6 separately?
Yes. IPv6 uses its own CIDR, its own ::/0 routes, and an egress-only internet gateway for outbound. The rules live in the same route table but are distinct from the IPv4 ones.
Why can't I delete my VPC?
Because something still references it - a subnet, gateway, ENI, or non-default security group. You must delete or detach dependents in reverse order before the VPC will delete.