Skip to content

VPC & networking

Most Alchemy apps on AWS never touch a VPC. Lambda functions serve public traffic from Function URLs, and bindings wire access to tables, buckets, and queues without a single subnet. Networking becomes your problem when you run an ECS Service or an EC2 Instance — both need a VPC, subnets, and security groups — or when you attach to anything that lives inside one.

Alchemy gives you two altitudes: the Network helper, which builds the standard public/private layout in one call, and the canonical primitives underneath it, for when you need explicit control.

Network creates a production-shaped VPC from the low-level primitives — VPC, internet gateway, one public and one private subnet per Availability Zone (2 AZs by default), route tables, routes, and associations — and returns every underlying resource so you can keep composing with raw AWS.EC2.* APIs:

const network = yield* AWS.EC2.Network("AppNetwork", {
cidrBlock: "10.42.0.0/16",
});

Subnet CIDRs are derived from the VPC block automatically, and the outputs you’ll reach for most — vpcId, publicSubnetIds, privateSubnetIds — sit right on the result.

Turn on NAT and gateway endpoints when private subnets need a path out:

const network = yield* AWS.EC2.Network("AppNetwork", {
cidrBlock: "10.42.0.0/16",
availabilityZones: 2,
nat: "single",
gatewayEndpoints: ["s3"],
});
yield* AWS.ECS.Service("ApiService", {
cluster,
task: {
taskDefinitionArn: apiTask.taskDefinitionArn,
containerName: apiTask.containerName,
port: apiTask.port,
},
vpcId: network.vpcId,
subnets: network.publicSubnetIds,
assignPublicIp: true,
});
  • nat: "none" (default) keeps private subnets isolated; "single" shares one NAT gateway; "per-az" creates one per Availability Zone for AZ-fault isolation.
  • gatewayEndpoints attaches free S3/DynamoDB gateway endpoints to the private route tables, so that traffic never crosses the NAT.

If Network’s layout fits, use it and stop reading. The rest of this page is the same network built by hand.

A Vpc is a private IPv4 range; an InternetGateway is its door to the internet:

const vpc = yield* AWS.EC2.Vpc("AppVpc", {
cidrBlock: "10.80.0.0/16",
enableDnsSupport: true,
enableDnsHostnames: true,
});
const igw = yield* AWS.EC2.InternetGateway("AppIgw", {
vpcId: vpc.vpcId,
});

The two DNS toggles let instances resolve names and receive public DNS hostnames — you’ll want both on for anything serving public traffic.

Subnets carve the VPC block into per-AZ ranges. mapPublicIpOnLaunch is what makes a subnet “public” from the instance’s point of view:

const subnetA = yield* AWS.EC2.Subnet("PublicSubnetA", {
vpcId: vpc.vpcId,
cidrBlock: "10.80.1.0/24",
availabilityZone: az1,
mapPublicIpOnLaunch: true,
});
const subnetB = yield* AWS.EC2.Subnet("PublicSubnetB", {
vpcId: vpc.vpcId,
cidrBlock: "10.80.2.0/24",
availabilityZone: az2,
mapPublicIpOnLaunch: true,
});

Two subnets in two AZs is the floor for anything fronted by a load balancer.

A subnet is only public once its RouteTable sends 0.0.0.0/0 to the internet gateway. Route adds the rule; RouteTableAssociation attaches the table to each subnet:

const routeTable = yield* AWS.EC2.RouteTable("PublicRouteTable", {
vpcId: vpc.vpcId,
});
yield* AWS.EC2.Route("PublicInternetRoute", {
routeTableId: routeTable.routeTableId,
destinationCidrBlock: "0.0.0.0/0",
gatewayId: igw.internetGatewayId,
});
yield* AWS.EC2.RouteTableAssociation("PublicAssocA", {
routeTableId: routeTable.routeTableId,
subnetId: subnetA.subnetId,
});
yield* AWS.EC2.RouteTableAssociation("PublicAssocB", {
routeTableId: routeTable.routeTableId,
subnetId: subnetB.subnetId,
});

A SecurityGroup is the per-resource firewall — the thing you attach to service ENIs, load balancers, and instances. Declare ingress and egress rules inline:

const securityGroup = yield* AWS.EC2.SecurityGroup("AppSg", {
vpcId: vpc.vpcId,
description: "app traffic",
ingress: [
{
ipProtocol: "tcp",
fromPort: 80,
toPort: 80,
cidrIpv4: "0.0.0.0/0",
description: "HTTP ingress",
},
],
egress: [
{
ipProtocol: "-1",
cidrIpv4: "0.0.0.0/0",
description: "all outbound",
},
],
});

Rules can also be managed as standalone SecurityGroupRule resources when different parts of your stack contribute rules to one group.

Private subnets reach the internet through a NatGateway living in a public subnet, addressed by an EIP, with the private route table pointing 0.0.0.0/0 at it:

const privateRouteTable = yield* AWS.EC2.RouteTable("PrivateRouteTable", {
vpcId: vpc.vpcId,
});
const eip = yield* AWS.EC2.EIP("NatEip", { domain: "vpc" });
const natGateway = yield* AWS.EC2.NatGateway("NatGateway", {
subnetId: subnetA.subnetId, // a public subnet
allocationId: eip.allocationId,
connectivityType: "public",
});
yield* AWS.EC2.Route("PrivateInternetRoute", {
routeTableId: privateRouteTable.routeTableId,
destinationCidrBlock: "0.0.0.0/0",
natGatewayId: natGateway.natGatewayId,
});

This is exactly what Network’s nat: "single" option builds ("per-az" repeats it per Availability Zone).

S3 and DynamoDB traffic from private subnets doesn’t need NAT at all — a Gateway VpcEndpoint routes it over AWS’s network for free:

yield* AWS.EC2.VpcEndpoint("S3Endpoint", {
vpcId: vpc.vpcId,
serviceName: "com.amazonaws.us-east-1.s3", // match your region
vpcEndpointType: "Gateway",
routeTableIds: [privateRouteTable.routeTableId],
});

Network’s gatewayEndpoints: ["s3", "dynamodb"] does the same against its private route tables.

ResourceWhat it does
VpcThe private IPv4 network everything else lives in
SubnetA per-AZ slice of the VPC’s address range
InternetGatewayTwo-way internet access for public subnets
EgressOnlyInternetGatewayOutbound-only IPv6 internet access
NatGatewayOutbound internet for private subnets
EIPA static public IPv4 address
RouteTable / Route / RouteTableAssociationWhere each subnet’s traffic goes
SecurityGroup / SecurityGroupRuleStateful per-resource firewall
VpcEndpointPrivate connectivity to AWS services
NetworkAcl / NetworkAclEntry / NetworkAclAssociationStateless subnet-level packet filtering
  • ECS — run containers in the network you just built, with an Alchemy-managed public ALB.
  • EC2 — launch instances into it, raw or hosting an Effect program.
  • Network reference — every prop and everything the helper returns.