DEV Community

Cover image for Day 15:Building Multi-Region VPC Peering with Terraform
Zakariyau Mukhtar
Zakariyau Mukhtar

Posted on

Day 15:Building Multi-Region VPC Peering with Terraform

Day 15 of my #30DaysOfAWSTerraform challenge was one of the most demanding so farnot because the concepts were impossible, but because this was the first time everything felt real-world messy. Today I worked extensively with VPC peering, multi-region infrastructure, routing, security groups, and Git conflicts the full DevOps experience.

What I Learned:

The core focus of today was understanding how VPC peering works beyond theory. I didn’t just connect two VPCs, I created three VPCs across three different AWS regions and connected all of them together:

  • Production VPC in us-east-1.
  • Testing VPC in us-west-2.
  • Development VPC in eu-west-2.

This immediately forced me to think about provider aliases, region isolation, and how AWS networking behaves when traffic crosses VPC and regional boundaries.
I also deepened my understanding of:

  • Route tables and why peering alone is not enough.
  • Internet Gateways and their role in outbound traffic.
  • Subnets and availability zones.
  • Security group ingress rules for controlled cross-VPC access.

Creating the VPCs (Terraform Code):

Each VPC was defined with its own provider alias and CIDR block. For example, the Production VPC:

resource "aws_vpc" "prod_vpc" {
  provider = aws.prod
  cidr_block = var.prod_vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support = true

  tags = {
    Name = "Production-VPC-${var.prod_A}"
    Environment = var.Environment
    Purpose = "VPC-Peering-Demo"
  }
}
Enter fullscreen mode Exit fullscreen mode

I repeated this pattern for Testing and Development VPCs, each tied to its own AWS region.

Subnets, Internet Gateways and Route Tables:

Each VPC had:

  • A public subnet.
  • An Internet Gateway.
  • A route table with 0.0.0.0/0 pointing to the IGW.

Example Production Route Table:

resource "aws_route_table" "prod_rt" {
  provider = aws.prod
  vpc_id = aws_vpc.prod_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.prod_igw.id
  }
}
Enter fullscreen mode Exit fullscreen mode

Without properly associating route tables with subnets, nothing works and Terraform won’t warn you. This was a critical lesson.

VPC Peering Connections (The Core of the Day):

I created three peering connections:

  • Production ↔ Testing.
  • Production ↔ Development.
  • Testing ↔ Development.

Each peering required:

  1. A requester connection.
  2. An accepter connection.
  3. Explicit routes in both VPC route tables.

Example Production to Testing peering:

resource "aws_vpc_peering_connection" "production_to_testing" {
  provider = aws.prod
  peer_vpc_id = aws_vpc.test_vpc.id
  vpc_id      = aws_vpc.prod_vpc.id
  peer_region = var.test_B
  auto_accept = false
}
Enter fullscreen mode Exit fullscreen mode

And the accepter:

resource "aws_vpc_peering_connection_accepter" "testing_to_production" {
  provider = aws.test
  vpc_peering_connection_id = aws_vpc_peering_connection.production_to_testing.id
  auto_accept = true
}
Enter fullscreen mode Exit fullscreen mode

Then the route entries the most commonly missed step:

resource "aws_route" "prod_to_test" {
  provider = aws.prod
  route_table_id = aws_route_table.prod_rt.id
  destination_cidr_block = var.test_vpc_cidr
  vpc_peering_connection_id = aws_vpc_peering_connection.production_to_testing.id
}
Enter fullscreen mode Exit fullscreen mode

Without this, peering exists but traffic dies silently.

Security Groups and EC2 Validation:

To validate connectivity, I deployed EC2 instances in each VPC with security groups that allowed:

  • SSH (port 22)
  • ICMP (ping)
  • Cross-VPC CIDR access

Example Production Security Group ingress:

ingress {
  description = "SSH from Test and Dev VPC"
  from_port = 22
  to_port = 22
  protocol = "tcp"
  cidr_blocks = [
    var.test_vpc_cidr,
    var.dev_vpc_cidr
  ]
}
Enter fullscreen mode Exit fullscreen mode

Each EC2 instance used Ubuntu 24.04 LTS, pulled via data sources, and included user-data scripts to install Apache and display region-specific info.

The Real Struggle: Git Merge Conflicts

This day was chaotic.

While pushing changes to Git, I ran into merge conflicts repeatedly. I ended up rewriting parts of the infrastructure three different times before I finally understood why the conflicts were happening.

It wasn’t Terraform’s fault.
It wasn’t AWS.
It was version control discipline.

That pain was worth it. I now understand merge conflicts at a deeper level not as errors, but as signals.

Final Thoughts

Day 15 taught me something important:
DevOps is not clean. It’s structured chaos.

VPC peering works beautifully when every dependency is correct. One missing route, one wrong CIDR, or one overlooked provider alias, and everything breaks silently.
Today, I didn’t just learn Terraform.
I learned how real infrastructure behaves under pressure.

On to Day 16.

Top comments (0)