Configure AWS Transit Gateway Route Table Associations

The aws:ec2transitgateway/routeTableAssociation:RouteTableAssociation resource, part of the Pulumi AWS provider, links Transit Gateway attachments to route tables, controlling which routes each attachment can access. This guide focuses on two capabilities: VPC attachment associations and Direct Connect Gateway associations.

Route table associations depend on existing Transit Gateway attachments and route tables. The examples are intentionally small. Combine them with your own Transit Gateway infrastructure and routing policies.

Associate a VPC attachment with a route table

Transit Gateway route tables control traffic flow between attached networks. Most deployments associate VPC attachments with route tables to enable inter-VPC routing.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.ec2.Vpc("example", {cidrBlock: "10.0.0.0/16"});
const exampleSubnet = new aws.ec2.Subnet("example", {
    vpcId: example.id,
    cidrBlock: "10.0.1.0/24",
});
const exampleTransitGateway = new aws.ec2transitgateway.TransitGateway("example", {description: "example"});
const exampleVpcAttachment = new aws.ec2transitgateway.VpcAttachment("example", {
    subnetIds: [exampleSubnet.id],
    transitGatewayId: exampleTransitGateway.id,
    vpcId: example.id,
});
const exampleRouteTable = new aws.ec2transitgateway.RouteTable("example", {transitGatewayId: exampleTransitGateway.id});
// Correct: Reference the VPC attachment ID directly
const exampleRouteTableAssociation = new aws.ec2transitgateway.RouteTableAssociation("example", {
    transitGatewayAttachmentId: exampleVpcAttachment.id,
    transitGatewayRouteTableId: exampleRouteTable.id,
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.Vpc("example", cidr_block="10.0.0.0/16")
example_subnet = aws.ec2.Subnet("example",
    vpc_id=example.id,
    cidr_block="10.0.1.0/24")
example_transit_gateway = aws.ec2transitgateway.TransitGateway("example", description="example")
example_vpc_attachment = aws.ec2transitgateway.VpcAttachment("example",
    subnet_ids=[example_subnet.id],
    transit_gateway_id=example_transit_gateway.id,
    vpc_id=example.id)
example_route_table = aws.ec2transitgateway.RouteTable("example", transit_gateway_id=example_transit_gateway.id)
# Correct: Reference the VPC attachment ID directly
example_route_table_association = aws.ec2transitgateway.RouteTableAssociation("example",
    transit_gateway_attachment_id=example_vpc_attachment.id,
    transit_gateway_route_table_id=example_route_table.id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2transitgateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		exampleSubnet, err := ec2.NewSubnet(ctx, "example", &ec2.SubnetArgs{
			VpcId:     example.ID(),
			CidrBlock: pulumi.String("10.0.1.0/24"),
		})
		if err != nil {
			return err
		}
		exampleTransitGateway, err := ec2transitgateway.NewTransitGateway(ctx, "example", &ec2transitgateway.TransitGatewayArgs{
			Description: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		exampleVpcAttachment, err := ec2transitgateway.NewVpcAttachment(ctx, "example", &ec2transitgateway.VpcAttachmentArgs{
			SubnetIds: pulumi.StringArray{
				exampleSubnet.ID(),
			},
			TransitGatewayId: exampleTransitGateway.ID(),
			VpcId:            example.ID(),
		})
		if err != nil {
			return err
		}
		exampleRouteTable, err := ec2transitgateway.NewRouteTable(ctx, "example", &ec2transitgateway.RouteTableArgs{
			TransitGatewayId: exampleTransitGateway.ID(),
		})
		if err != nil {
			return err
		}
		// Correct: Reference the VPC attachment ID directly
		_, err = ec2transitgateway.NewRouteTableAssociation(ctx, "example", &ec2transitgateway.RouteTableAssociationArgs{
			TransitGatewayAttachmentId: exampleVpcAttachment.ID(),
			TransitGatewayRouteTableId: exampleRouteTable.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 example = new Aws.Ec2.Vpc("example", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var exampleSubnet = new Aws.Ec2.Subnet("example", new()
    {
        VpcId = example.Id,
        CidrBlock = "10.0.1.0/24",
    });

    var exampleTransitGateway = new Aws.Ec2TransitGateway.TransitGateway("example", new()
    {
        Description = "example",
    });

    var exampleVpcAttachment = new Aws.Ec2TransitGateway.VpcAttachment("example", new()
    {
        SubnetIds = new[]
        {
            exampleSubnet.Id,
        },
        TransitGatewayId = exampleTransitGateway.Id,
        VpcId = example.Id,
    });

    var exampleRouteTable = new Aws.Ec2TransitGateway.RouteTable("example", new()
    {
        TransitGatewayId = exampleTransitGateway.Id,
    });

    // Correct: Reference the VPC attachment ID directly
    var exampleRouteTableAssociation = new Aws.Ec2TransitGateway.RouteTableAssociation("example", new()
    {
        TransitGatewayAttachmentId = exampleVpcAttachment.Id,
        TransitGatewayRouteTableId = exampleRouteTable.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.ec2transitgateway.TransitGateway;
import com.pulumi.aws.ec2transitgateway.TransitGatewayArgs;
import com.pulumi.aws.ec2transitgateway.VpcAttachment;
import com.pulumi.aws.ec2transitgateway.VpcAttachmentArgs;
import com.pulumi.aws.ec2transitgateway.RouteTable;
import com.pulumi.aws.ec2transitgateway.RouteTableArgs;
import com.pulumi.aws.ec2transitgateway.RouteTableAssociation;
import com.pulumi.aws.ec2transitgateway.RouteTableAssociationArgs;
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 Vpc("example", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var exampleSubnet = new Subnet("exampleSubnet", SubnetArgs.builder()
            .vpcId(example.id())
            .cidrBlock("10.0.1.0/24")
            .build());

        var exampleTransitGateway = new TransitGateway("exampleTransitGateway", TransitGatewayArgs.builder()
            .description("example")
            .build());

        var exampleVpcAttachment = new VpcAttachment("exampleVpcAttachment", VpcAttachmentArgs.builder()
            .subnetIds(exampleSubnet.id())
            .transitGatewayId(exampleTransitGateway.id())
            .vpcId(example.id())
            .build());

        var exampleRouteTable = new RouteTable("exampleRouteTable", RouteTableArgs.builder()
            .transitGatewayId(exampleTransitGateway.id())
            .build());

        // Correct: Reference the VPC attachment ID directly
        var exampleRouteTableAssociation = new RouteTableAssociation("exampleRouteTableAssociation", RouteTableAssociationArgs.builder()
            .transitGatewayAttachmentId(exampleVpcAttachment.id())
            .transitGatewayRouteTableId(exampleRouteTable.id())
            .build());

    }
}
resources:
  example:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16
  exampleSubnet:
    type: aws:ec2:Subnet
    name: example
    properties:
      vpcId: ${example.id}
      cidrBlock: 10.0.1.0/24
  exampleTransitGateway:
    type: aws:ec2transitgateway:TransitGateway
    name: example
    properties:
      description: example
  exampleVpcAttachment:
    type: aws:ec2transitgateway:VpcAttachment
    name: example
    properties:
      subnetIds:
        - ${exampleSubnet.id}
      transitGatewayId: ${exampleTransitGateway.id}
      vpcId: ${example.id}
  exampleRouteTable:
    type: aws:ec2transitgateway:RouteTable
    name: example
    properties:
      transitGatewayId: ${exampleTransitGateway.id}
  # Correct: Reference the VPC attachment ID directly
  exampleRouteTableAssociation:
    type: aws:ec2transitgateway:RouteTableAssociation
    name: example
    properties:
      transitGatewayAttachmentId: ${exampleVpcAttachment.id}
      transitGatewayRouteTableId: ${exampleRouteTable.id}

The transitGatewayAttachmentId references the VPC attachment’s ID directly. The transitGatewayRouteTableId specifies which route table controls this attachment’s routing. When the VPC attachment is replaced, this association recreates automatically to maintain consistency.

Associate a Direct Connect Gateway with a route table

Organizations connecting on-premises networks via Direct Connect associate the Direct Connect Gateway attachment with a route table to enable hybrid routing.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.directconnect.Gateway("example", {
    name: "example",
    amazonSideAsn: "64512",
});
const exampleTransitGateway = new aws.ec2transitgateway.TransitGateway("example", {description: "example"});
const exampleGatewayAssociation = new aws.directconnect.GatewayAssociation("example", {
    dxGatewayId: example.id,
    associatedGatewayId: exampleTransitGateway.id,
    allowedPrefixes: ["10.0.0.0/16"],
});
const exampleRouteTable = new aws.ec2transitgateway.RouteTable("example", {transitGatewayId: exampleTransitGateway.id});
// Correct: Reference the attachment ID directly from the association resource
const exampleRouteTableAssociation = new aws.ec2transitgateway.RouteTableAssociation("example", {
    transitGatewayAttachmentId: exampleGatewayAssociation.transitGatewayAttachmentId,
    transitGatewayRouteTableId: exampleRouteTable.id,
});
import pulumi
import pulumi_aws as aws

example = aws.directconnect.Gateway("example",
    name="example",
    amazon_side_asn="64512")
example_transit_gateway = aws.ec2transitgateway.TransitGateway("example", description="example")
example_gateway_association = aws.directconnect.GatewayAssociation("example",
    dx_gateway_id=example.id,
    associated_gateway_id=example_transit_gateway.id,
    allowed_prefixes=["10.0.0.0/16"])
example_route_table = aws.ec2transitgateway.RouteTable("example", transit_gateway_id=example_transit_gateway.id)
# Correct: Reference the attachment ID directly from the association resource
example_route_table_association = aws.ec2transitgateway.RouteTableAssociation("example",
    transit_gateway_attachment_id=example_gateway_association.transit_gateway_attachment_id,
    transit_gateway_route_table_id=example_route_table.id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/directconnect"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2transitgateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := directconnect.NewGateway(ctx, "example", &directconnect.GatewayArgs{
			Name:          pulumi.String("example"),
			AmazonSideAsn: pulumi.String("64512"),
		})
		if err != nil {
			return err
		}
		exampleTransitGateway, err := ec2transitgateway.NewTransitGateway(ctx, "example", &ec2transitgateway.TransitGatewayArgs{
			Description: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		exampleGatewayAssociation, err := directconnect.NewGatewayAssociation(ctx, "example", &directconnect.GatewayAssociationArgs{
			DxGatewayId:         example.ID(),
			AssociatedGatewayId: exampleTransitGateway.ID(),
			AllowedPrefixes: pulumi.StringArray{
				pulumi.String("10.0.0.0/16"),
			},
		})
		if err != nil {
			return err
		}
		exampleRouteTable, err := ec2transitgateway.NewRouteTable(ctx, "example", &ec2transitgateway.RouteTableArgs{
			TransitGatewayId: exampleTransitGateway.ID(),
		})
		if err != nil {
			return err
		}
		// Correct: Reference the attachment ID directly from the association resource
		_, err = ec2transitgateway.NewRouteTableAssociation(ctx, "example", &ec2transitgateway.RouteTableAssociationArgs{
			TransitGatewayAttachmentId: exampleGatewayAssociation.TransitGatewayAttachmentId,
			TransitGatewayRouteTableId: exampleRouteTable.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 example = new Aws.DirectConnect.Gateway("example", new()
    {
        Name = "example",
        AmazonSideAsn = "64512",
    });

    var exampleTransitGateway = new Aws.Ec2TransitGateway.TransitGateway("example", new()
    {
        Description = "example",
    });

    var exampleGatewayAssociation = new Aws.DirectConnect.GatewayAssociation("example", new()
    {
        DxGatewayId = example.Id,
        AssociatedGatewayId = exampleTransitGateway.Id,
        AllowedPrefixes = new[]
        {
            "10.0.0.0/16",
        },
    });

    var exampleRouteTable = new Aws.Ec2TransitGateway.RouteTable("example", new()
    {
        TransitGatewayId = exampleTransitGateway.Id,
    });

    // Correct: Reference the attachment ID directly from the association resource
    var exampleRouteTableAssociation = new Aws.Ec2TransitGateway.RouteTableAssociation("example", new()
    {
        TransitGatewayAttachmentId = exampleGatewayAssociation.TransitGatewayAttachmentId,
        TransitGatewayRouteTableId = exampleRouteTable.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.directconnect.Gateway;
import com.pulumi.aws.directconnect.GatewayArgs;
import com.pulumi.aws.ec2transitgateway.TransitGateway;
import com.pulumi.aws.ec2transitgateway.TransitGatewayArgs;
import com.pulumi.aws.directconnect.GatewayAssociation;
import com.pulumi.aws.directconnect.GatewayAssociationArgs;
import com.pulumi.aws.ec2transitgateway.RouteTable;
import com.pulumi.aws.ec2transitgateway.RouteTableArgs;
import com.pulumi.aws.ec2transitgateway.RouteTableAssociation;
import com.pulumi.aws.ec2transitgateway.RouteTableAssociationArgs;
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 Gateway("example", GatewayArgs.builder()
            .name("example")
            .amazonSideAsn("64512")
            .build());

        var exampleTransitGateway = new TransitGateway("exampleTransitGateway", TransitGatewayArgs.builder()
            .description("example")
            .build());

        var exampleGatewayAssociation = new GatewayAssociation("exampleGatewayAssociation", GatewayAssociationArgs.builder()
            .dxGatewayId(example.id())
            .associatedGatewayId(exampleTransitGateway.id())
            .allowedPrefixes("10.0.0.0/16")
            .build());

        var exampleRouteTable = new RouteTable("exampleRouteTable", RouteTableArgs.builder()
            .transitGatewayId(exampleTransitGateway.id())
            .build());

        // Correct: Reference the attachment ID directly from the association resource
        var exampleRouteTableAssociation = new RouteTableAssociation("exampleRouteTableAssociation", RouteTableAssociationArgs.builder()
            .transitGatewayAttachmentId(exampleGatewayAssociation.transitGatewayAttachmentId())
            .transitGatewayRouteTableId(exampleRouteTable.id())
            .build());

    }
}
resources:
  example:
    type: aws:directconnect:Gateway
    properties:
      name: example
      amazonSideAsn: 64512
  exampleTransitGateway:
    type: aws:ec2transitgateway:TransitGateway
    name: example
    properties:
      description: example
  exampleGatewayAssociation:
    type: aws:directconnect:GatewayAssociation
    name: example
    properties:
      dxGatewayId: ${example.id}
      associatedGatewayId: ${exampleTransitGateway.id}
      allowedPrefixes:
        - 10.0.0.0/16
  exampleRouteTable:
    type: aws:ec2transitgateway:RouteTable
    name: example
    properties:
      transitGatewayId: ${exampleTransitGateway.id}
  # Correct: Reference the attachment ID directly from the association resource
  exampleRouteTableAssociation:
    type: aws:ec2transitgateway:RouteTableAssociation
    name: example
    properties:
      transitGatewayAttachmentId: ${exampleGatewayAssociation.transitGatewayAttachmentId}
      transitGatewayRouteTableId: ${exampleRouteTable.id}

The transitGatewayAttachmentId comes from the GatewayAssociation resource’s transitGatewayAttachmentId attribute. Reference this attribute directly rather than using a data source; data sources can cause unnecessary recreation when unrelated properties like allowedPrefixes change.

Beyond these examples

These snippets focus on specific route table association features: VPC and Direct Connect Gateway attachment associations, and route table assignment for traffic control. They’re intentionally minimal rather than full Transit Gateway topologies.

The examples reference pre-existing infrastructure such as Transit Gateway and route tables, and VPC attachments or Direct Connect Gateway associations. They focus on creating the association rather than provisioning the underlying attachments.

To keep things focused, common association patterns are omitted, including:

  • Replacing existing associations (replaceExistingAssociation)
  • Cross-account Transit Gateway scenarios
  • VPN attachment associations
  • Peering attachment associations

These omissions are intentional: the goal is to illustrate how each association type is wired, not provide drop-in networking modules. See the Transit Gateway Route Table Association resource reference for all available configuration options.

Let's configure AWS Transit Gateway Route Table Associations

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 & Pitfalls
Why does my Direct Connect Gateway route table association keep recreating unnecessarily?
Using the aws.ec2transitgateway.getDirectConnectGatewayAttachment data source causes recreation when unrelated attributes like allowed_prefixes change. Instead, reference the transitGatewayAttachmentId attribute directly from the aws.directconnect.GatewayAssociation resource.
Why does the route table association recreate when my attachment changes?
When transitGatewayAttachmentId changes (for example, when a VPC attachment is replaced), the association recreates to maintain consistency. This is expected and correct behavior.
Configuration & Setup
How should I reference attachment IDs when creating route table associations?
Always reference attachment IDs directly from the attachment resource. For Direct Connect Gateways, use the transitGatewayAttachmentId attribute from aws.directconnect.GatewayAssociation. For VPC attachments, use the id attribute from aws.ec2transitgateway.VpcAttachment. Avoid using data sources or lifecycle rules that might make the attachment ID unknown during planning.
When should I use the replaceExistingAssociation parameter?
Use replaceExistingAssociation (set to true) when working with Transit Gateways shared into your account to remove existing route table associations before creating new ones. For Transit Gateways you own, use the transitGatewayDefaultRouteTableAssociation argument on the aws.ec2transitgateway.VpcAttachment resource instead.
Resource Behavior
What properties are immutable after creating a route table association?
Both transitGatewayAttachmentId and transitGatewayRouteTableId are immutable and cannot be changed after creation.

Using a different cloud?

Explore networking guides for other cloud providers: