AWS VPC Peering and Transit Gateway with Terraform

· 2 min read · Terraform & OpenTofu Tutorials

Connecting VPCs #

When you have more than one VPC (one per environment, per team or per account), you need a way for them to talk using private IP addresses. The two main options are VPC peering and AWS Transit Gateway. The CIDR blocks of the connected VPCs must not overlap, so plan them in advance (VPC tutorial).

Comparison #

VPC peering Transit Gateway
Topology One connection between two VPCs A hub that connects many VPCs, VPNs and Direct Connect
Transitive routing No (A-B and B-C does not mean A-C) Yes
Number of connections N x (N-1) / 2 for a full mesh One attachment per VPC
Cost No hourly charge, only data transfer between AZs or regions Hourly charge per attachment plus data processed
Best for Two or three VPCs Many VPCs or hybrid networks

VPC peering in the same account and region #

peering.tf
resource "aws_vpc_peering_connection" "app_to_shared" {
  vpc_id      = aws_vpc.app.id
  peer_vpc_id = aws_vpc.shared.id
  auto_accept = true

  tags = {
    Name = "app-to-shared"
  }
}

auto_accept only works when both VPCs are in the same account and region. After the peering exists, add routes on both sides: the connection does nothing without them.

peering.tf
resource "aws_route" "app_to_shared" {
  route_table_id            = aws_route_table.app_private.id
  destination_cidr_block    = aws_vpc.shared.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.app_to_shared.id
}

resource "aws_route" "shared_to_app" {
  route_table_id            = aws_route_table.shared_private.id
  destination_cidr_block    = aws_vpc.app.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.app_to_shared.id
}

Then allow the traffic in the security groups (reference the CIDR of the other VPC). Routing is explained in routing tables.

Peering across accounts #

The requester creates the connection and the owner of the other VPC accepts it, using a second provider with credentials for that account:

peering-cross-account.tf
provider "aws" {
  alias   = "peer"
  region  = "eu-west-1"
  profile = "other-account"
}

data "aws_caller_identity" "peer" {
  provider = aws.peer
}

resource "aws_vpc_peering_connection" "cross" {
  vpc_id        = aws_vpc.app.id
  peer_vpc_id   = var.peer_vpc_id
  peer_owner_id = data.aws_caller_identity.peer.account_id
}

resource "aws_vpc_peering_connection_accepter" "cross" {
  provider                  = aws.peer
  vpc_peering_connection_id = aws_vpc_peering_connection.cross.id
  auto_accept               = true
}

Transit Gateway #

tgw.tf
resource "aws_ec2_transit_gateway" "main" {
  description                     = "Central hub"
  default_route_table_association = "enable"
  default_route_table_propagation = "enable"
  dns_support                     = "enable"

  tags = {
    Name = "ditwl-tgw"
  }
}

resource "aws_ec2_transit_gateway_vpc_attachment" "vpc" {
  for_each = {
    app    = { vpc_id = aws_vpc.app.id,    subnet_ids = aws_subnet.app_tgw[*].id }
    shared = { vpc_id = aws_vpc.shared.id, subnet_ids = aws_subnet.shared_tgw[*].id }
  }

  transit_gateway_id = aws_ec2_transit_gateway.main.id
  vpc_id             = each.value.vpc_id
  subnet_ids         = each.value.subnet_ids
}

With default association and propagation, every attached VPC can reach every other VPC. The routes inside the VPCs still need to point to the gateway:

tgw.tf
resource "aws_route" "app_to_tgw" {
  route_table_id         = aws_route_table.app_private.id
  destination_cidr_block = "10.0.0.0/8"   # all your internal networks
  transit_gateway_id     = aws_ec2_transit_gateway.main.id

  depends_on = [aws_ec2_transit_gateway_vpc_attachment.vpc]
}

For isolation (for example production cannot reach development), disable the defaults and create separate Transit Gateway route tables and associations.

Share it across accounts #

Use AWS Resource Access Manager (aws_ram_resource_share and aws_ram_principal_association) to share the Transit Gateway with other accounts in your organization (multi-account), then each account creates its attachment.

Costs #

Transit Gateway charges per attachment per hour and per GB processed, so a small setup with two VPCs is much cheaper with peering. Peering has no hourly fee. Data transfer between AZs and regions applies to both. Consider VPC endpoints for access to specific services and estimate with Infracost.

#AWS #VPC #Network #Terraform #OpenTofu