1. Overview and rationale

  • LocalStack emulates EC2/VPC APIs locally, so Terraform can create VPCs, subnets, route tables, and gateways just like on AWS.
  • The AWS CLI can talk to LocalStack by using --endpoint-url http://localhost:4566, letting you validate resources with the exact same commands you’d run against real AWS.
  • This keeps your workflow close to production: same Terraform provider, same CLI, different endpoint.

2. Run LocalStack with Docker Compose

Create docker-compose.yml:

version: "3.8"

services:
  localstack:
    image: localstack/localstack:latest
    container_name: localstack
    ports:
      - "4566:4566"              # Edge port: all AWS APIs
      - "4510-4559:4510-4559"    # Optional service ports
    environment:
      - SERVICES=ec2             # Add more: ec2,lambda,rds,ecs,...
      - AWS_DEFAULT_REGION=us-east-1
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

Start LocalStack:

docker compose up -d

LocalStack now exposes EC2/VPC on http://localhost:4566 (the edge endpoint).


3. Terraform VPC configuration

Keep Terraform AWS‑idiomatic and only change the endpoint.

providers.tf

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
  s3_use_path_style           = true

  endpoints {
    ec2 = "http://localhost:4566"
  }
}

main.tf

resource "aws_vpc" "demo" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "localstack-demo-vpc"
  }
}

resource "aws_subnet" "public_az1" {
  vpc_id                  = aws_vpc.demo.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "localstack-demo-public-az1"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.demo.id

  tags = {
    Name = "localstack-demo-igw"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.demo.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "localstack-demo-public-rt"
  }
}

resource "aws_route_table_association" "public_az1_assoc" {
  subnet_id      = aws_subnet.public_az1.id
  route_table_id = aws_route_table.public.id
}

output "vpc_id" {
  value = aws_vpc.demo.id
}

output "public_subnet_id" {
  value = aws_subnet.public_az1.id
}

Apply:

terraform init
terraform apply

This models the classic “public subnet” layout so the same module can later be pointed at real AWS with only a provider change.

4. Configure AWS CLI for LocalStack

You can use the stock AWS CLI v2 and override the endpoint.

  1. Configure a local profile (optional but tidy):
aws configure --profile localstack
# AWS Access Key ID: test
# AWS Secret Access Key: test
# Default region name: us-east-1
# Default output format: json
  1. For each command, add:
--endpoint-url http://localhost:4566 --profile localstack

This tells the CLI to send EC2 API calls to LocalStack instead of AWS.

If you prefer less typing, you can define an alias (e.g. in your shell):

alias awslocal='aws --endpoint-url http://localhost:4566 --profile localstack'

This is conceptually the same as the awslocal wrapper LocalStack provides, but you stay completely within standard AWS CLI semantics.


5. Validating the VPC with AWS CLI

After terraform apply, use the CLI to verify each piece of the VPC.

5.1 List VPCs

aws ec2 describe-vpcs --endpoint-url http://localhost:4566 --profile localstack

or with the alias:

awslocal ec2 describe-vpcs

Check for a VPC with:

  • CidrBlock = 10.0.0.0/16
  • Tag Name = localstack-demo-vpc

The shape of the output is identical to real AWS describe-vpcs.

5.2 List subnets

awslocal ec2 describe-subnets

Confirm a subnet exists with:

  • CidrBlock = 10.0.1.0/24
  • AvailabilityZone = us-east-1a
  • Tag Name = localstack-demo-public-az1

5.3 List Internet Gateways

awslocal ec2 describe-internet-gateways

You should see an IGW whose Attachments includes your VPC ID and has tag localstack-demo-igw.

5.4 List route tables

awslocal ec2 describe-route-tables

Verify:

  • A route table tagged localstack-demo-public-rt.
  • A route with DestinationCidrBlock 0.0.0.0/0 and GatewayId set to your IGW ID.
  • An association to your public subnet ID.

These commands mirror the official AWS CLI usage for EC2, just with the endpoint overridden.

6. Why this testing approach scales

  • Uses only Terraform + AWS CLI, tools you already depend on in real environments.
  • Easy to script: you can wrap the CLI checks into bash scripts or CI jobs to assert your VPC configuration in LocalStack before promoting to AWS.
  • Mental model stays aligned with production AWS: same commands, same JSON structures, just a different base URL.