Configure AWS VPC Route Tables

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 FREE

Frequently Asked Questions

Common Issues & Troubleshooting
Why am I seeing constant diffs in my route table?
Check if you’re mixing up 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.
Can I use inline routes with standalone aws.ec2.Route resources?
No, using inline 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
How do I remove all managed routes from a route table?
Set routes to an empty array (routes: []). Omitting the routes argument entirely means ignoring existing routes, not removing them.
What's the difference between omitting routes and using an empty array?
Omitting the routes argument ignores any existing routes (leaves them unchanged). An empty routes array removes all managed routes.
How do I adopt an existing AWS-created local route?
Include the route in your 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
Can I use propagatingVgws with aws.ec2.VpnGatewayRoutePropagation?
No, using the propagatingVgws argument deletes any propagations not explicitly listed, conflicting with aws.ec2.VpnGatewayRoutePropagation resources. Omit propagatingVgws when using the separate resource.
Configuration & Immutability
Can I change the VPC after creating a route table?
No, 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: