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 attachment ID must be referenced directly from the attachment resource to avoid unnecessary recreation. 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 directly via its id attribute. The transitGatewayRouteTableId specifies which route table controls this attachment’s routing. When the attachment is replaced, this association is automatically recreated 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 must reference the transitGatewayAttachmentId attribute directly from the GatewayAssociation resource. Avoid using data sources to retrieve the attachment ID; changes to unrelated properties like allowedPrefixes can trigger unnecessary recreation when using data sources.

Beyond these examples

These snippets focus on specific route table association features: VPC and Direct Connect Gateway attachment associations. They’re intentionally minimal rather than full Transit Gateway deployments.

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

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

  • Replacing existing associations (replaceExistingAssociation for shared Transit Gateways)
  • Default route table association control (handled via VpcAttachment resource)

These omissions are intentional: the goal is to illustrate how attachment-to-route-table binding works, 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

Attachment References & Best Practices
How should I reference Transit Gateway attachment IDs to avoid recreation issues?
Always reference attachment IDs directly from the attachment resource itself. 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, as they can cause unnecessary recreation when unrelated attributes change or make the attachment ID unknown during planning.
Why does my Direct Connect Gateway 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 (available in v6.5.0+).
Configuration & Shared Gateways
When should I use the replaceExistingAssociation parameter?
Use replaceExistingAssociation (default: false) when working with Transit Gateways shared into your account, to remove any existing route table association before creating a new one. For Transit Gateways you own, use the transitGatewayDefaultRouteTableAssociation argument on the aws.ec2transitgateway.VpcAttachment resource instead.
What properties can't be changed after creating the association?
Both transitGatewayAttachmentId and transitGatewayRouteTableId are immutable. Changing either property requires recreating the association resource.
Resource Behavior & Lifecycle
Why does my route table association recreate when the VPC attachment changes?
When the transitGatewayAttachmentId changes (for example, when a VPC attachment is replaced), the association resource recreates automatically. This is expected behavior to maintain consistency between the attachment and its route table association.

Using a different cloud?

Explore networking guides for other cloud providers: