The aws:ec2/routeTable:RouteTable resource, part of the Pulumi AWS provider, defines a VPC routing table with inline route entries that direct traffic between subnets and external networks. This guide focuses on three capabilities: inline route definitions for internet and egress-only gateways, managing AWS-created local routes, and redirecting routes to custom targets.
Route tables belong to VPCs and reference gateways or network interfaces as route targets. The examples are intentionally small. Combine them with your own VPC infrastructure and subnet associations.
Route traffic to internet and egress-only gateways
Most VPC deployments need routing tables that direct traffic to an internet gateway for IPv4 and an egress-only gateway for IPv6 outbound traffic.
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 property defines an array of route objects. Each route specifies a destination CIDR block and a target. The gatewayId points to an internet gateway for IPv4 traffic, while egressOnlyGatewayId handles IPv6 outbound traffic. The vpcId associates the table with your VPC.
Remove all managed routes from a table
Sometimes you need to clear all routes without deleting the table itself, such as when transitioning to standalone Route resources.
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 tells Pulumi to ignore existing routes. Use an empty array when you want explicit control over route removal.
Adopt AWS-created local routes for management
AWS automatically creates local routes for VPC CIDR blocks. You can bring these under Pulumi management by explicitly declaring them.
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 local gateway is a special AWS-managed target that routes traffic within the VPC. By declaring it explicitly with gatewayId set to “local”, you bring the route under Pulumi management. This enables you to later modify the route’s target.
Redirect local routes to network interfaces
After adopting a local route, you can redirect it to custom targets like network interfaces for traffic inspection.
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}
This configuration changes the route target from “local” to a network interface. Traffic matching the VPC CIDR block now flows through the specified network interface instead of using AWS’s default local routing. The networkInterfaceId property replaces gatewayId to specify the new target.
Beyond these examples
These snippets focus on specific route table features: inline route definitions, IPv4 and IPv6 routing, and local route adoption and modification. They’re intentionally minimal rather than full VPC networking configurations.
The examples may reference pre-existing infrastructure such as VPCs, internet gateways, egress-only gateways, and network interfaces for route targets. They focus on configuring the route table rather than provisioning everything around it.
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
- NAT gateway routing
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
Resource Conflicts & Compatibility
aws.ec2.RouteTable inline route blocks together with aws.ec2.Route resources. This causes conflicts and overwrites rules. Choose one approach: either define routes inline in the RouteTable or use separate Route resources.propagatingVgws and aws.ec2.VpnGatewayRoutePropagation together is not supported. The RouteTable resource deletes any propagating gateways not explicitly listed in propagatingVgws. Omit the propagatingVgws argument when using the separate VpnGatewayRoutePropagation resource.Configuration & State Management
gatewayId field, but returns the correct parameters in the state, causing perpetual diffs. Check that you’re using gatewayId for Internet Gateways and natGatewayId for NAT Gateways, not mixing them.routes to an empty array (routes: []). Note that omitting the routes argument entirely means ignoring existing routes, not removing them.vpcId property is immutable and cannot be changed after the route table is created.Route Adoption & Updates
cidrBlock matching your VPC CIDR and gatewayId set to "local". This brings the AWS-managed route under Pulumi management.gatewayId: "local" to networkInterfaceId, and later change it back to "local" if needed.Using a different cloud?
Explore networking guides for other cloud providers: