Amazon VPC via SDK: Your First Network
A VPC is your private network on AWS - the address space and boundaries other resources live inside.
Search across all documentation pages
A VPC is your private network on AWS - the address space and boundaries other resources live inside.
This page creates the three foundational pieces from code: a VPC, a subnet within it, and a security group with one ingress rule. Then it tears everything down in the correct order.
All of this uses the ec2 client, because VPC networking lives under the EC2 API surface.
Quick-reference recipe card - copy-paste ready.
# --- Python (boto3) ---
import boto3
ec2 = boto3.client("ec2", region_name="us-east-1")
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
ec2.get_waiter("vpc_available").wait(VpcIds=[vpc["VpcId"]])
print("vpc:", vpc["VpcId"])// --- TypeScript (AWS SDK v3) ---
import { EC2Client, CreateVpcCommand, waitUntilVpcAvailable } from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({ region: "us-east-1" });
const { Vpc } = await ec2.send(new CreateVpcCommand({ CidrBlock: "10.0.0.0/16" }));
await waitUntilVpcAvailable({ client: ec2, maxWaitTime: 60 }, { VpcIds: [Vpc!.VpcId!] });
console.log("vpc:", Vpc?.VpcId);When to reach for this:
Create a VPC, carve out a subnet in one Availability Zone, create a security group, add an ingress rule for HTTPS, then tear it all down in reverse order.
# --- Python (boto3) ---
import boto3
ec2 = boto3.client("ec2", region_name="us-east-1")
# 1. VPC: a private /16 address range.
vpc_id = ec2.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
ec2.get_waiter("vpc_available").wait(VpcIds=[vpc_id])
# 2. Subnet: a /24 slice in one Availability Zone.
subnet_id = ec2.create_subnet(
VpcId=vpc_id, CidrBlock="10.0.1.0/24",
AvailabilityZone="us-east-1a",
)["Subnet"]["SubnetId"]
# 3. Security group: stateful allow-list, default deny.
sg_id = ec2.create_security_group(
GroupName="web-sg", Description="allow https in", VpcId=vpc_id,
)["GroupId"]
# 4. One ingress rule: HTTPS from anywhere (scope this down in production).
ec2.authorize_security_group_ingress(
GroupId=sg_id,
IpPermissions=[{
"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}],
}],
)
# 5. Tear down in reverse order.
ec2.delete_security_group(GroupId=sg_id)
ec2.delete_subnet(SubnetId=subnet_id)
ec2.delete_vpc(VpcId=vpc_id)// --- TypeScript (AWS SDK v3) ---
import {
EC2Client, CreateVpcCommand, CreateSubnetCommand, CreateSecurityGroupCommand,
AuthorizeSecurityGroupIngressCommand, DeleteSecurityGroupCommand,
DeleteSubnetCommand, DeleteVpcCommand, waitUntilVpcAvailable,
} from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({ region: "us-east-1" });
// 1. VPC: a private /16 address range.
const vpc = await ec2.send(new CreateVpcCommand({ CidrBlock: "10.0.0.0/16" }));
const vpcId = vpc.Vpc!.VpcId!;
await waitUntilVpcAvailable({ client: ec2, maxWaitTime: 60 }, { VpcIds: [vpcId] });
// 2. Subnet: a /24 slice in one Availability Zone.
const subnet = await ec2.send(new CreateSubnetCommand({
VpcId: vpcId, CidrBlock: "10.0.1.0/24", AvailabilityZone: "us-east-1a",
}));
const subnetId = subnet.Subnet!.SubnetId!;
// 3. Security group: stateful allow-list, default deny.
const sg = await ec2.send(new CreateSecurityGroupCommand({
GroupName: "web-sg", Description: "allow https in", VpcId: vpcId,
}));
const sgId = sg.GroupId!;
// 4. One ingress rule: HTTPS from anywhere (scope this down in production).
await ec2.send(new AuthorizeSecurityGroupIngressCommand({
GroupId: sgId,
IpPermissions: [{
IpProtocol: "tcp", FromPort: 443, ToPort: 443,
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
}],
}));
// 5. Tear down in reverse order.
await ec2.send(new DeleteSecurityGroupCommand({ GroupId: sgId }));
await ec2.send(new DeleteSubnetCommand({ SubnetId: subnetId }));
await ec2.send(new DeleteVpcCommand({ VpcId: vpcId }));What this demonstrates:
10.0.0.0/16.A subnet is only "public" if its route table sends 0.0.0.0/0 to an internet gateway. Databases and internal services belong in private subnets with no such route.
# --- Python (boto3) ---
# Inspect a subnet's public-IP behavior and AZ.
s = ec2.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]
print(s["AvailabilityZone"], s["MapPublicIpOnLaunch"])// --- TypeScript (AWS SDK v3) ---
import { DescribeSubnetsCommand } from "@aws-sdk/client-ec2";
const out = await ec2.send(new DescribeSubnetsCommand({ SubnetIds: [subnetId] }));
const s = out.Subnets![0];
console.log(s.AvailabilityZone, s.MapPublicIpOnLaunch);MapPublicIpOnLaunch controls whether instances there get a public IP; keep it off for private tiers.
For internal traffic, allow another security group as the source rather than a CIDR. That way "the web tier may reach the database" is expressed by group, and it keeps working as IPs change.
0.0.0.0/0 on sensitive ports is a common exposure. Fix: restrict CidrIp to known ranges, or reference a security group as the source.| Alternative | Use When | Don't Use When |
|---|---|---|
| Custom VPC via SDK (this page) | You need controlled, isolated networking | A quick test the default VPC already serves |
| Default VPC | Fast experiments and simple public workloads | Production isolation and private tiers |
| CloudFormation / CDK / Terraform | Versioned, reproducible network infrastructure | One-off imperative setup |
| VPC peering / Transit Gateway | Connecting multiple VPCs or accounts | A single self-contained network |
| Network ACLs | Stateless, subnet-level allow/deny rules | Instance-level stateful rules (use security groups) |
Historically VPC networking is part of the EC2 API. So CreateVpc, CreateSubnet, and CreateSecurityGroup are all operations on the ec2 client, not a separate vpc client.
A VPC is the whole private network defined by a CIDR block. A subnet is a smaller slice of that range bound to a single Availability Zone, where you actually place resources.
Its route table. A subnet is public only if it routes 0.0.0.0/0 to an internet gateway. Without that route it is private, which is where databases and internal services belong.
If you allow an inbound request, the response is automatically allowed back out, and vice versa. You do not need matching egress rules for replies to allowed traffic.
Security groups deny all inbound traffic until you add explicit ingress rules, and allow all outbound by default. Add a rule for each port and source you want to permit.
Allow another security group as the source rather than an IP range. Expressing "web tier may reach database tier" by group keeps working as instance IPs change.
A VPC cannot be deleted while it contains subnets, security groups (beyond the default), gateways, or other dependents. Delete those first, in reverse of creation order.
No. Each region has a default VPC that works for quick tests and simple public workloads. Create a custom VPC when you need isolation, private subnets, or specific addressing.
A security group is a stateful, instance-level firewall. A network ACL is a stateless, subnet-level allow/deny list. Most workloads rely on security groups and leave ACLs at defaults.
Create a subnet in each Availability Zone you want to use and spread resources across them. A single subnet lives in one AZ, so multiple subnets give you AZ-level redundancy.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 24, 2026