The aws:ec2/routeTable:RouteTable resource, part of the Pulumi AWS provider, defines a VPC routing table that controls how traffic flows between subnets and external networks. This guide focuses on three capabilities: inline route definitions for gateways, route lifecycle management, and adopting AWS-created local routes.
Route tables belong to a VPC and reference gateways, network interfaces, or other routing targets. The examples are intentionally small. Combine them with your own VPC, subnets, and gateway infrastructure.
Route traffic to internet and egress-only gateways
Most VPC deployments route IPv4 traffic through an internet gateway and IPv6 traffic through an egress-only gateway to enable internet access.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ec2.RouteTable("example", {
vpcId: exampleAwsVpc.id,
routes: [
{
cidrBlock: "10.0.1.0/24",
gatewayId: exampleAwsInternetGateway.id,
},
{
ipv6CidrBlock: "::/0",
egressOnlyGatewayId: exampleAwsEgressOnlyInternetGateway.id,
},
],
tags: {
Name: "example",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ec2.RouteTable("example",
vpc_id=example_aws_vpc["id"],
routes=[
{
"cidr_block": "10.0.1.0/24",
"gateway_id": example_aws_internet_gateway["id"],
},
{
"ipv6_cidr_block": "::/0",
"egress_only_gateway_id": example_aws_egress_only_internet_gateway["id"],
},
],
tags={
"Name": "example",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ec2.NewRouteTable(ctx, "example", &ec2.RouteTableArgs{
VpcId: pulumi.Any(exampleAwsVpc.Id),
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: pulumi.String("10.0.1.0/24"),
GatewayId: pulumi.Any(exampleAwsInternetGateway.Id),
},
&ec2.RouteTableRouteArgs{
Ipv6CidrBlock: pulumi.String("::/0"),
EgressOnlyGatewayId: pulumi.Any(exampleAwsEgressOnlyInternetGateway.Id),
},
},
Tags: pulumi.StringMap{
"Name": pulumi.String("example"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ec2.RouteTable("example", new()
{
VpcId = exampleAwsVpc.Id,
Routes = new[]
{
new Aws.Ec2.Inputs.RouteTableRouteArgs
{
CidrBlock = "10.0.1.0/24",
GatewayId = exampleAwsInternetGateway.Id,
},
new Aws.Ec2.Inputs.RouteTableRouteArgs
{
Ipv6CidrBlock = "::/0",
EgressOnlyGatewayId = exampleAwsEgressOnlyInternetGateway.Id,
},
},
Tags =
{
{ "Name", "example" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.RouteTable;
import com.pulumi.aws.ec2.RouteTableArgs;
import com.pulumi.aws.ec2.inputs.RouteTableRouteArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new RouteTable("example", RouteTableArgs.builder()
.vpcId(exampleAwsVpc.id())
.routes(
RouteTableRouteArgs.builder()
.cidrBlock("10.0.1.0/24")
.gatewayId(exampleAwsInternetGateway.id())
.build(),
RouteTableRouteArgs.builder()
.ipv6CidrBlock("::/0")
.egressOnlyGatewayId(exampleAwsEgressOnlyInternetGateway.id())
.build())
.tags(Map.of("Name", "example"))
.build());
}
}
resources:
example:
type: aws:ec2:RouteTable
properties:
vpcId: ${exampleAwsVpc.id}
routes:
- cidrBlock: 10.0.1.0/24
gatewayId: ${exampleAwsInternetGateway.id}
- ipv6CidrBlock: ::/0
egressOnlyGatewayId: ${exampleAwsEgressOnlyInternetGateway.id}
tags:
Name: example
The routes array defines inline route entries. Each route specifies a destination CIDR block and a target. The gatewayId points to an internet gateway for IPv4 traffic, while egressOnlyGatewayId handles IPv6 egress-only traffic. The vpcId associates the table with your VPC.
Remove all managed routes from a table
To clear all routes without deleting the table, pass an empty routes array.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.ec2.RouteTable("example", {
vpcId: exampleAwsVpc.id,
routes: [],
tags: {
Name: "example",
},
});
import pulumi
import pulumi_aws as aws
example = aws.ec2.RouteTable("example",
vpc_id=example_aws_vpc["id"],
routes=[],
tags={
"Name": "example",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ec2.NewRouteTable(ctx, "example", &ec2.RouteTableArgs{
VpcId: pulumi.Any(exampleAwsVpc.Id),
Routes: ec2.RouteTableRouteArray{},
Tags: pulumi.StringMap{
"Name": pulumi.String("example"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Ec2.RouteTable("example", new()
{
VpcId = exampleAwsVpc.Id,
Routes = new[] {},
Tags =
{
{ "Name", "example" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.RouteTable;
import com.pulumi.aws.ec2.RouteTableArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new RouteTable("example", RouteTableArgs.builder()
.vpcId(exampleAwsVpc.id())
.routes()
.tags(Map.of("Name", "example"))
.build());
}
}
resources:
example:
type: aws:ec2:RouteTable
properties:
vpcId: ${exampleAwsVpc.id}
routes: []
tags:
Name: example
Setting routes to an empty array removes all managed routes. This differs from omitting the routes property, which leaves existing routes unchanged. Use this when transitioning to standalone Route resources or temporarily isolating subnets.
Adopt an AWS-created local route
AWS automatically creates local routes for VPC CIDR blocks. You can adopt these into Pulumi management to later modify their targets.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const test = new aws.ec2.Vpc("test", {cidrBlock: "10.1.0.0/16"});
const testRouteTable = new aws.ec2.RouteTable("test", {
vpcId: test.id,
routes: [{
cidrBlock: "10.1.0.0/16",
gatewayId: "local",
}],
});
import pulumi
import pulumi_aws as aws
test = aws.ec2.Vpc("test", cidr_block="10.1.0.0/16")
test_route_table = aws.ec2.RouteTable("test",
vpc_id=test.id,
routes=[{
"cidr_block": "10.1.0.0/16",
"gateway_id": "local",
}])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
test, err := ec2.NewVpc(ctx, "test", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.1.0.0/16"),
})
if err != nil {
return err
}
_, err = ec2.NewRouteTable(ctx, "test", &ec2.RouteTableArgs{
VpcId: test.ID(),
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: pulumi.String("10.1.0.0/16"),
GatewayId: pulumi.String("local"),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var test = new Aws.Ec2.Vpc("test", new()
{
CidrBlock = "10.1.0.0/16",
});
var testRouteTable = new Aws.Ec2.RouteTable("test", new()
{
VpcId = test.Id,
Routes = new[]
{
new Aws.Ec2.Inputs.RouteTableRouteArgs
{
CidrBlock = "10.1.0.0/16",
GatewayId = "local",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.RouteTable;
import com.pulumi.aws.ec2.RouteTableArgs;
import com.pulumi.aws.ec2.inputs.RouteTableRouteArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var test = new Vpc("test", VpcArgs.builder()
.cidrBlock("10.1.0.0/16")
.build());
var testRouteTable = new RouteTable("testRouteTable", RouteTableArgs.builder()
.vpcId(test.id())
.routes(RouteTableRouteArgs.builder()
.cidrBlock("10.1.0.0/16")
.gatewayId("local")
.build())
.build());
}
}
resources:
test:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.1.0.0/16
testRouteTable:
type: aws:ec2:RouteTable
name: test
properties:
vpcId: ${test.id}
routes:
- cidrBlock: 10.1.0.0/16
gatewayId: local
The gatewayId value “local” represents AWS’s default local gateway for intra-VPC routing. By explicitly defining this route, you bring it under Pulumi management, enabling future modifications.
Retarget a local route to a network interface
After adopting a local route, you can change its target to route traffic through a network interface.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const test = new aws.ec2.Vpc("test", {cidrBlock: "10.1.0.0/16"});
const testSubnet = new aws.ec2.Subnet("test", {
cidrBlock: "10.1.1.0/24",
vpcId: test.id,
});
const testNetworkInterface = new aws.ec2.NetworkInterface("test", {subnetId: testSubnet.id});
const testRouteTable = new aws.ec2.RouteTable("test", {
vpcId: test.id,
routes: [{
cidrBlock: test.cidrBlock,
networkInterfaceId: testNetworkInterface.id,
}],
});
import pulumi
import pulumi_aws as aws
test = aws.ec2.Vpc("test", cidr_block="10.1.0.0/16")
test_subnet = aws.ec2.Subnet("test",
cidr_block="10.1.1.0/24",
vpc_id=test.id)
test_network_interface = aws.ec2.NetworkInterface("test", subnet_id=test_subnet.id)
test_route_table = aws.ec2.RouteTable("test",
vpc_id=test.id,
routes=[{
"cidr_block": test.cidr_block,
"network_interface_id": test_network_interface.id,
}])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
test, err := ec2.NewVpc(ctx, "test", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.1.0.0/16"),
})
if err != nil {
return err
}
testSubnet, err := ec2.NewSubnet(ctx, "test", &ec2.SubnetArgs{
CidrBlock: pulumi.String("10.1.1.0/24"),
VpcId: test.ID(),
})
if err != nil {
return err
}
testNetworkInterface, err := ec2.NewNetworkInterface(ctx, "test", &ec2.NetworkInterfaceArgs{
SubnetId: testSubnet.ID(),
})
if err != nil {
return err
}
_, err = ec2.NewRouteTable(ctx, "test", &ec2.RouteTableArgs{
VpcId: test.ID(),
Routes: ec2.RouteTableRouteArray{
&ec2.RouteTableRouteArgs{
CidrBlock: test.CidrBlock,
NetworkInterfaceId: testNetworkInterface.ID(),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var test = new Aws.Ec2.Vpc("test", new()
{
CidrBlock = "10.1.0.0/16",
});
var testSubnet = new Aws.Ec2.Subnet("test", new()
{
CidrBlock = "10.1.1.0/24",
VpcId = test.Id,
});
var testNetworkInterface = new Aws.Ec2.NetworkInterface("test", new()
{
SubnetId = testSubnet.Id,
});
var testRouteTable = new Aws.Ec2.RouteTable("test", new()
{
VpcId = test.Id,
Routes = new[]
{
new Aws.Ec2.Inputs.RouteTableRouteArgs
{
CidrBlock = test.CidrBlock,
NetworkInterfaceId = testNetworkInterface.Id,
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.ec2.NetworkInterface;
import com.pulumi.aws.ec2.NetworkInterfaceArgs;
import com.pulumi.aws.ec2.RouteTable;
import com.pulumi.aws.ec2.RouteTableArgs;
import com.pulumi.aws.ec2.inputs.RouteTableRouteArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var test = new Vpc("test", VpcArgs.builder()
.cidrBlock("10.1.0.0/16")
.build());
var testSubnet = new Subnet("testSubnet", SubnetArgs.builder()
.cidrBlock("10.1.1.0/24")
.vpcId(test.id())
.build());
var testNetworkInterface = new NetworkInterface("testNetworkInterface", NetworkInterfaceArgs.builder()
.subnetId(testSubnet.id())
.build());
var testRouteTable = new RouteTable("testRouteTable", RouteTableArgs.builder()
.vpcId(test.id())
.routes(RouteTableRouteArgs.builder()
.cidrBlock(test.cidrBlock())
.networkInterfaceId(testNetworkInterface.id())
.build())
.build());
}
}
resources:
test:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.1.0.0/16
testRouteTable:
type: aws:ec2:RouteTable
name: test
properties:
vpcId: ${test.id}
routes:
- cidrBlock: ${test.cidrBlock}
networkInterfaceId: ${testNetworkInterface.id}
testSubnet:
type: aws:ec2:Subnet
name: test
properties:
cidrBlock: 10.1.1.0/24
vpcId: ${test.id}
testNetworkInterface:
type: aws:ec2:NetworkInterface
name: test
properties:
subnetId: ${testSubnet.id}
The networkInterfaceId property replaces the local gateway target, directing VPC traffic through a specific network interface. This enables custom routing through appliances or middleboxes. The route could later be changed back to “local” if needed.
Beyond these examples
These snippets focus on specific route table features: inline route definitions, IPv4 and IPv6 gateway routing, and local route adoption and retargeting. They’re intentionally minimal rather than full VPC networking configurations.
The examples reference pre-existing infrastructure such as VPCs, internet gateways and egress-only gateways, and network interfaces for retargeting. They focus on configuring the route table rather than provisioning the surrounding network.
To keep things focused, common route table patterns are omitted, including:
- VPN gateway route propagation (propagatingVgws)
- Standalone Route resources (aws.ec2.Route)
- Route table associations with subnets
- Tags for organization and cost tracking
These omissions are intentional: the goal is to illustrate how each route table feature is wired, not provide drop-in networking modules. See the Route Table resource reference for all available configuration options.
Let's configure AWS VPC Route Tables
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Common Issues & Troubleshooting
gatewayId and natGatewayId. The AWS API accepts a NAT Gateway ID in the gatewayId field, but returns the correct parameters, causing perpetual diffs. Use gatewayId for Internet Gateways and VPN Gateways, and natGatewayId for NAT Gateways.route blocks in aws.ec2.RouteTable with standalone aws.ec2.Route resources causes conflicts and overwrites rules. Choose one approach or the other.Route Management
routes to an empty array (routes: []). Omitting the routes argument entirely means ignoring existing routes, not removing them.routes argument ignores any existing routes (leaves them unchanged). An empty routes array removes all managed routes.routes array with cidrBlock matching your VPC CIDR and gatewayId set to "local". You can later update the target to a different resource like a network interface.VPN Gateway Propagation
propagatingVgws argument deletes any propagations not explicitly listed, conflicting with aws.ec2.VpnGatewayRoutePropagation resources. Omit propagatingVgws when using the separate resource.Configuration & Immutability
vpcId is immutable and cannot be changed after creation. You must create a new route table in the desired VPC.Using a different cloud?
Explore networking guides for other cloud providers: