| 1 | --- |
| 2 | title: VPC & networking |
| 3 | description: The Network helper for a production-shaped VPC in one call, and the full set of EC2 networking primitives — VPCs, subnets, gateways, routes, security groups, and endpoints — for explicit control. |
| 4 | --- |
| 5 | |
| 6 | Most Alchemy apps on AWS never touch a VPC. Lambda functions |
| 7 | serve public traffic from Function URLs, and bindings wire |
| 8 | access to tables, buckets, and queues without a single subnet. |
| 9 | Networking becomes your problem when you run an |
| 10 | [ECS Service](/aws/compute/ecs), an [EKS Cluster](/aws/compute/eks), or an |
| 11 | [EC2 Instance](/aws/compute/ec2) — all |
| 12 | need a VPC, subnets, and security groups — or when you attach to |
| 13 | anything that lives inside one. |
| 14 | |
| 15 | Alchemy gives you two altitudes: the **`Network` helper**, which |
| 16 | builds the standard public/private layout in one call, and the |
| 17 | **canonical primitives** underneath it, for when you need |
| 18 | explicit control. |
| 19 | |
| 20 | ## The `Network` helper |
| 21 | |
| 22 | [`Network`](/providers/aws/ec2/network) creates a |
| 23 | production-shaped VPC from the low-level primitives — VPC, |
| 24 | internet gateway, one public and one private subnet per |
| 25 | Availability Zone (2 AZs by default), route tables, routes, and |
| 26 | associations — and returns every underlying resource so you can |
| 27 | keep composing with raw `AWS.EC2.*` APIs: |
| 28 | |
| 29 | ```typescript |
| 30 | const network = yield* AWS.EC2.Network("AppNetwork", { |
| 31 | cidrBlock: "10.42.0.0/16", |
| 32 | }); |
| 33 | ``` |
| 34 | |
| 35 | Subnet CIDRs are derived from the VPC block automatically, and |
| 36 | the outputs you'll reach for most — `vpcId`, `publicSubnetIds`, |
| 37 | `privateSubnetIds` — sit right on the result. |
| 38 | |
| 39 | Turn on NAT and gateway endpoints when private subnets need a |
| 40 | path out: |
| 41 | |
| 42 | ```typescript |
| 43 | const network = yield* AWS.EC2.Network("AppNetwork", { |
| 44 | cidrBlock: "10.42.0.0/16", |
| 45 | availabilityZones: 2, |
| 46 | nat: "single", |
| 47 | gatewayEndpoints: ["s3"], |
| 48 | }); |
| 49 | |
| 50 | yield* AWS.ECS.Service("ApiService", { |
| 51 | cluster, |
| 52 | task: { |
| 53 | taskDefinitionArn: apiTask.taskDefinitionArn, |
| 54 | containerName: apiTask.containerName, |
| 55 | port: apiTask.port, |
| 56 | }, |
| 57 | vpcId: network.vpcId, |
| 58 | subnets: network.publicSubnetIds, |
| 59 | assignPublicIp: true, |
| 60 | }); |
| 61 | ``` |
| 62 | |
| 63 | - `nat: "none"` (default) keeps private subnets isolated; |
| 64 | `"single"` shares one NAT gateway; `"per-az"` creates one per |
| 65 | Availability Zone for AZ-fault isolation. |
| 66 | - `gatewayEndpoints` attaches free S3/DynamoDB gateway endpoints |
| 67 | to the private route tables, so that traffic never crosses the |
| 68 | NAT. |
| 69 | |
| 70 | If `Network`'s layout fits, use it and stop reading. The rest of |
| 71 | this page is the same network built by hand. |
| 72 | |
| 73 | ## Building it by hand |
| 74 | |
| 75 | ### VPC and internet gateway |
| 76 | |
| 77 | A [`Vpc`](/providers/aws/ec2/vpc) is a private IPv4 range; an |
| 78 | [`InternetGateway`](/providers/aws/ec2/internetgateway) is its |
| 79 | door to the internet: |
| 80 | |
| 81 | ```typescript |
| 82 | const vpc = yield* AWS.EC2.Vpc("AppVpc", { |
| 83 | cidrBlock: "10.80.0.0/16", |
| 84 | enableDnsSupport: true, |
| 85 | enableDnsHostnames: true, |
| 86 | }); |
| 87 | |
| 88 | const igw = yield* AWS.EC2.InternetGateway("AppIgw", { |
| 89 | vpcId: vpc.vpcId, |
| 90 | }); |
| 91 | ``` |
| 92 | |
| 93 | The two DNS toggles let instances resolve names and receive |
| 94 | public DNS hostnames — you'll want both on for anything serving |
| 95 | public traffic. |
| 96 | |
| 97 | ### Subnets |
| 98 | |
| 99 | [`Subnet`](/providers/aws/ec2/subnet)s carve the VPC block into |
| 100 | per-AZ ranges. `mapPublicIpOnLaunch` is what makes a subnet |
| 101 | "public" from the instance's point of view: |
| 102 | |
| 103 | ```typescript |
| 104 | const subnetA = yield* AWS.EC2.Subnet("PublicSubnetA", { |
| 105 | vpcId: vpc.vpcId, |
| 106 | cidrBlock: "10.80.1.0/24", |
| 107 | availabilityZone: az1, |
| 108 | mapPublicIpOnLaunch: true, |
| 109 | }); |
| 110 | |
| 111 | const subnetB = yield* AWS.EC2.Subnet("PublicSubnetB", { |
| 112 | vpcId: vpc.vpcId, |
| 113 | cidrBlock: "10.80.2.0/24", |
| 114 | availabilityZone: az2, |
| 115 | mapPublicIpOnLaunch: true, |
| 116 | }); |
| 117 | ``` |
| 118 | |
| 119 | Two subnets in two AZs is the floor for anything fronted by a |
| 120 | load balancer. |
| 121 | |
| 122 | ### Routing |
| 123 | |
| 124 | A subnet is only public once its |
| 125 | [`RouteTable`](/providers/aws/ec2/routetable) sends `0.0.0.0/0` |
| 126 | to the internet gateway. [`Route`](/providers/aws/ec2/route) adds |
| 127 | the rule; |
| 128 | [`RouteTableAssociation`](/providers/aws/ec2/routetableassociation) |
| 129 | attaches the table to each subnet: |
| 130 | |
| 131 | ```typescript |
| 132 | const routeTable = yield* AWS.EC2.RouteTable("PublicRouteTable", { |
| 133 | vpcId: vpc.vpcId, |
| 134 | }); |
| 135 | |
| 136 | yield* AWS.EC2.Route("PublicInternetRoute", { |
| 137 | routeTableId: routeTable.routeTableId, |
| 138 | destinationCidrBlock: "0.0.0.0/0", |
| 139 | gatewayId: igw.internetGatewayId, |
| 140 | }); |
| 141 | |
| 142 | yield* AWS.EC2.RouteTableAssociation("PublicAssocA", { |
| 143 | routeTableId: routeTable.routeTableId, |
| 144 | subnetId: subnetA.subnetId, |
| 145 | }); |
| 146 | yield* AWS.EC2.RouteTableAssociation("PublicAssocB", { |
| 147 | routeTableId: routeTable.routeTableId, |
| 148 | subnetId: subnetB.subnetId, |
| 149 | }); |
| 150 | ``` |
| 151 | |
| 152 | ### Security groups |
| 153 | |
| 154 | A [`SecurityGroup`](/providers/aws/ec2/securitygroup) is the |
| 155 | per-resource firewall — the thing you attach to service ENIs, |
| 156 | load balancers, and instances. Declare ingress and egress rules |
| 157 | inline: |
| 158 | |
| 159 | ```typescript |
| 160 | const securityGroup = yield* AWS.EC2.SecurityGroup("AppSg", { |
| 161 | vpcId: vpc.vpcId, |
| 162 | description: "app traffic", |
| 163 | ingress: [ |
| 164 | { |
| 165 | ipProtocol: "tcp", |
| 166 | fromPort: 80, |
| 167 | toPort: 80, |
| 168 | cidrIpv4: "0.0.0.0/0", |
| 169 | description: "HTTP ingress", |
| 170 | }, |
| 171 | ], |
| 172 | egress: [ |
| 173 | { |
| 174 | ipProtocol: "-1", |
| 175 | cidrIpv4: "0.0.0.0/0", |
| 176 | description: "all outbound", |
| 177 | }, |
| 178 | ], |
| 179 | }); |
| 180 | ``` |
| 181 | |
| 182 | Rules can also be managed as standalone |
| 183 | [`SecurityGroupRule`](/providers/aws/ec2/securitygrouprule) |
| 184 | resources when different parts of your stack contribute rules to |
| 185 | one group. |
| 186 | |
| 187 | ## Private egress with NAT |
| 188 | |
| 189 | Private subnets reach the internet through a |
| 190 | [`NatGateway`](/providers/aws/ec2/natgateway) living in a |
| 191 | *public* subnet, addressed by an [`EIP`](/providers/aws/ec2/eip), |
| 192 | with the private route table pointing `0.0.0.0/0` at it: |
| 193 | |
| 194 | ```typescript |
| 195 | const privateRouteTable = yield* AWS.EC2.RouteTable("PrivateRouteTable", { |
| 196 | vpcId: vpc.vpcId, |
| 197 | }); |
| 198 | |
| 199 | const eip = yield* AWS.EC2.EIP("NatEip", { domain: "vpc" }); |
| 200 | |
| 201 | const natGateway = yield* AWS.EC2.NatGateway("NatGateway", { |
| 202 | subnetId: subnetA.subnetId, // a public subnet |
| 203 | allocationId: eip.allocationId, |
| 204 | connectivityType: "public", |
| 205 | }); |
| 206 | |
| 207 | yield* AWS.EC2.Route("PrivateInternetRoute", { |
| 208 | routeTableId: privateRouteTable.routeTableId, |
| 209 | destinationCidrBlock: "0.0.0.0/0", |
| 210 | natGatewayId: natGateway.natGatewayId, |
| 211 | }); |
| 212 | ``` |
| 213 | |
| 214 | This is exactly what `Network`'s `nat: "single"` option builds |
| 215 | (`"per-az"` repeats it per Availability Zone). |
| 216 | |
| 217 | ## Gateway endpoints |
| 218 | |
| 219 | S3 and DynamoDB traffic from private subnets doesn't need NAT at |
| 220 | all — a Gateway [`VpcEndpoint`](/providers/aws/ec2/vpcendpoint) |
| 221 | routes it over AWS's network for free: |
| 222 | |
| 223 | ```typescript |
| 224 | yield* AWS.EC2.VpcEndpoint("S3Endpoint", { |
| 225 | vpcId: vpc.vpcId, |
| 226 | serviceName: "com.amazonaws.us-east-1.s3", // match your region |
| 227 | vpcEndpointType: "Gateway", |
| 228 | routeTableIds: [privateRouteTable.routeTableId], |
| 229 | }); |
| 230 | ``` |
| 231 | |
| 232 | `Network`'s `gatewayEndpoints: ["s3", "dynamodb"]` does the same |
| 233 | against its private route tables. |
| 234 | |
| 235 | ## The full toolkit |
| 236 | |
| 237 | | Resource | What it does | |
| 238 | | --- | --- | |
| 239 | | [`Vpc`](/providers/aws/ec2/vpc) | The private IPv4 network everything else lives in | |
| 240 | | [`Subnet`](/providers/aws/ec2/subnet) | A per-AZ slice of the VPC's address range | |
| 241 | | [`InternetGateway`](/providers/aws/ec2/internetgateway) | Two-way internet access for public subnets | |
| 242 | | [`EgressOnlyInternetGateway`](/providers/aws/ec2/egressonlyinternetgateway) | Outbound-only IPv6 internet access | |
| 243 | | [`NatGateway`](/providers/aws/ec2/natgateway) | Outbound internet for private subnets | |
| 244 | | [`EIP`](/providers/aws/ec2/eip) | A static public IPv4 address | |
| 245 | | [`RouteTable`](/providers/aws/ec2/routetable) / [`Route`](/providers/aws/ec2/route) / [`RouteTableAssociation`](/providers/aws/ec2/routetableassociation) | Where each subnet's traffic goes | |
| 246 | | [`SecurityGroup`](/providers/aws/ec2/securitygroup) / [`SecurityGroupRule`](/providers/aws/ec2/securitygrouprule) | Stateful per-resource firewall | |
| 247 | | [`VpcEndpoint`](/providers/aws/ec2/vpcendpoint) | Private connectivity to AWS services | |
| 248 | | [`NetworkAcl`](/providers/aws/ec2/networkacl) / [`NetworkAclEntry`](/providers/aws/ec2/networkaclentry) / [`NetworkAclAssociation`](/providers/aws/ec2/networkaclassociation) | Stateless subnet-level packet filtering | |
| 249 | |
| 250 | ## Where next |
| 251 | |
| 252 | - [ECS](/aws/compute/ecs) — run containers in the network you just |
| 253 | built, with an Alchemy-managed public ALB. |
| 254 | - [EC2](/aws/compute/ec2) — launch instances into it, raw or hosting an |
| 255 | Effect program. |
| 256 | - [`Network` reference](/providers/aws/ec2/network) — every prop |
| 257 | and everything the helper returns. |