TL;DR

  • LocalStack emula 80+ servicios AWS localmente — testea S3, Lambda, DynamoDB sin costos cloud
  • Usa LocalStack para iteracion rapida y CI; usa AWS real para tests de integracion antes de produccion
  • El error #1: tratar LocalStack como equivalente a produccion (es para testing, no 100% paridad)

Ideal para: Equipos con infraestructura AWS que quieren loops de feedback mas rapidos y menores costos CI Omite si: Necesitas garantias exactas de comportamiento AWS o usas servicios que LocalStack no soporta Tiempo de lectura: 10 minutos

Tus tests de Terraform contra AWS real toman 8 minutos por ejecucion. Las facturas de CI suben. Los desarrolladores esperan que los recursos cloud se provisionen antes de validar sus cambios. Mientras tanto, errores de configuracion de S3 bucket llegan a produccion porque testear se sentia “muy lento.”

LocalStack cambia esta ecuacion. Provee un stack AWS local que corre en Docker, soportando 80+ servicios. Tus tests corren en segundos, no minutos. Costos CI bajan. Loops de feedback se acortan. Pero necesitas entender que LocalStack hace y que no garantiza.

El Problema Real

Testear contra AWS real tiene costos:

Tiempo: Provisionar una instancia RDS toma minutos. Crear un VPC con subnets, NAT gateways y rutas — mas minutos. Desarrolladores evitan testear porque es lento.

Dinero: CI ejecutando Terraform applies contra AWS real acumula costos. Recursos de test olvidados corren toda la noche. Atribucion de costos es difusa.

Flakiness: Problemas de red, rate limits y consistencia eventual causan fallos intermitentes. Tests que pasan localmente fallan en CI.

Conflictos de ambiente: Multiples desarrolladores o jobs CI compitiendo por la misma cuenta AWS crean conflictos de nombres de recursos y corrupcion de estado.

LocalStack aborda esto proveyendo un ambiente AWS local, aislado que provisiona instantaneamente y no cuesta nada.

Setup de LocalStack

LocalStack corre como contenedor Docker. Setup basico:

# Iniciar LocalStack
docker run -d \
  --name localstack \
  -p 4566:4566 \
  -e SERVICES=s3,dynamodb,lambda,sqs,sns,iam \
  -e DEBUG=1 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  localstack/localstack:latest

# Verificar que esta corriendo
curl http://localhost:4566/_localstack/health

Para docker-compose (recomendado para proyectos):

# docker-compose.yml
version: '3.8'

services:
  localstack:
    image: localstack/localstack:latest
    ports:

      - "4566:4566"
    environment:

      - SERVICES=s3,dynamodb,lambda,sqs,sns,iam,secretsmanager
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:

      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./localstack-data:/var/lib/localstack"

Inicia con docker-compose up -d.

Terraform con LocalStack

Configura Terraform para usar endpoints de LocalStack:

# providers.tf
provider "aws" {
  access_key                  = "test"
  secret_key                  = "test"
  region                      = "us-east-1"

  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    s3             = "http://localhost:4566"
    dynamodb       = "http://localhost:4566"
    lambda         = "http://localhost:4566"
    iam            = "http://localhost:4566"
    sqs            = "http://localhost:4566"
    sns            = "http://localhost:4566"
    secretsmanager = "http://localhost:4566"
  }
}

Mejor enfoque — usa providers especificos por ambiente:

# providers.tf
locals {
  is_localstack = var.environment == "localstack"
}

provider "aws" {
  region = var.aws_region

  access_key = local.is_localstack ? "test" : null
  secret_key = local.is_localstack ? "test" : null

  s3_use_path_style           = local.is_localstack
  skip_credentials_validation = local.is_localstack
  skip_metadata_api_check     = local.is_localstack
  skip_requesting_account_id  = local.is_localstack

  dynamic "endpoints" {
    for_each = local.is_localstack ? [1] : []
    content {
      s3             = "http://localhost:4566"
      dynamodb       = "http://localhost:4566"
      lambda         = "http://localhost:4566"
      iam            = "http://localhost:4566"
      sqs            = "http://localhost:4566"
      sns            = "http://localhost:4566"
      secretsmanager = "http://localhost:4566"
    }
  }
}

Terratest con LocalStack

Terratest puede apuntar a LocalStack:

package test

import (
    "testing"
    "os"

    "github.com/gruntwork-io/terratest/modules/aws"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestS3BucketWithLocalStack(t *testing.T) {
    t.Parallel()

    // Override AWS SDK para usar LocalStack
    os.Setenv("AWS_ACCESS_KEY_ID", "test")
    os.Setenv("AWS_SECRET_ACCESS_KEY", "test")
    os.Setenv("AWS_DEFAULT_REGION", "us-east-1")

    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/s3-bucket",
        Vars: map[string]interface{}{
            "environment":  "localstack",
            "bucket_name":  "test-bucket-" + random.UniqueId(),
        },
    }

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Verificar que el bucket existe
    bucketName := terraform.Output(t, terraformOptions, "bucket_name")

    // Usar endpoint custom para LocalStack
    awsConfig := aws.NewConfig(
        aws.WithEndpointResolver(
            aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
                return aws.Endpoint{URL: "http://localhost:4566"}, nil
            }),
        ),
    )

    // Assert propiedades del bucket
    assert.True(t, aws.AssertS3BucketExists(t, "us-east-1", bucketName))
}

Testing Python con moto

Para aplicaciones Python, moto provee mocking de AWS:

import boto3
import pytest
from moto import mock_aws

@mock_aws
def test_s3_bucket_creation():
    # Crear mock S3
    s3 = boto3.client('s3', region_name='us-east-1')

    # Crear bucket
    s3.create_bucket(Bucket='test-bucket')

    # Verificar
    response = s3.list_buckets()
    bucket_names = [b['Name'] for b in response['Buckets']]
    assert 'test-bucket' in bucket_names

@mock_aws
def test_dynamodb_table():
    # Crear mock DynamoDB
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')

    # Crear tabla
    table = dynamodb.create_table(
        TableName='test-table',
        KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
        AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
        BillingMode='PAY_PER_REQUEST'
    )

    # Escribir y leer
    table.put_item(Item={'id': '123', 'data': 'test'})
    response = table.get_item(Key={'id': '123'})

    assert response['Item']['data'] == 'test'

@mock_aws
def test_lambda_invocation():
    # Crear mock Lambda
    lambda_client = boto3.client('lambda', region_name='us-east-1')
    iam = boto3.client('iam', region_name='us-east-1')

    # Crear role (requerido para Lambda)
    iam.create_role(
        RoleName='test-role',
        AssumeRolePolicyDocument='{}',
    )

    # Crear funcion
    lambda_client.create_function(
        FunctionName='test-function',
        Runtime='python3.9',
        Role='arn:aws:iam::123456789:role/test-role',
        Handler='handler.main',
        Code={'ZipFile': b'fake code'},
    )

    # Verificar que la funcion existe
    response = lambda_client.list_functions()
    function_names = [f['FunctionName'] for f in response['Functions']]
    assert 'test-function' in function_names

Integracion CI/CD

GitHub Actions con LocalStack:

name: Infrastructure Tests

on:
  pull_request:
    paths:

      - 'terraform/**'
      - 'tests/**'

jobs:
  localstack-tests:
    runs-on: ubuntu-latest
    services:
      localstack:
        image: localstack/localstack:latest
        ports:

          - 4566:4566
        env:
          SERVICES: s3,dynamodb,lambda,sqs,sns,iam
          DEBUG: 1

    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Wait for LocalStack
        run: |
          timeout 60 bash -c 'until curl -s http://localhost:4566/_localstack/health | grep -q "running"; do sleep 2; done'

      - name: Run Terraform Tests
        run: |
          cd tests
          go test -v -timeout 10m ./...
        env:
          AWS_ACCESS_KEY_ID: test
          AWS_SECRET_ACCESS_KEY: test
          AWS_DEFAULT_REGION: us-east-1
          LOCALSTACK_ENDPOINT: http://localhost:4566

  # Tests AWS reales solo corren en rama main
  aws-integration:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:

      - uses: actions/checkout@v4
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/ci-role
          aws-region: us-east-1
      - name: Run Integration Tests
        run: go test -v -tags=integration ./tests/integration/...

LocalStack Pro vs Community

LocalStack Pro agrega servicios que no estan en el tier gratuito:

FeatureCommunityPro
S3, DynamoDB, SQS, SNS, LambdaSiSi
IAM (completo)ParcialSi
RDS, AuroraNoSi
EKS, ECSNoSi
CloudFormation (completo)ParcialSi
PersistenciaBasicaCompleta
Cloud Pods (snapshots)NoSi

Para la mayoria de testing Terraform, la edicion Community cubre los servicios comunes. Pro vale la pena si usas mucho RDS, EKS o necesitas persistencia en CI.

Que LocalStack No Cubre

LocalStack no es AWS. Diferencias clave:

Evaluacion IAM: El IAM de LocalStack es simplificado. Policies que fallan en AWS real podrian pasar en LocalStack.

Consistencia eventual: S3 en LocalStack es inmediatamente consistente. S3 real tiene consistencia eventual para algunas operaciones.

Limites de servicio: LocalStack no enforza cuotas de servicio AWS. Tu test podria pasar pero fallar en produccion por limites.

Networking: VPCs, subnets, security groups funcionan diferente. Network ACLs y routing complejo no estan completamente emulados.

Caracteristicas de performance: LocalStack no simula latencia AWS, throttling o cold starts con precision.

Estrategia de Testing: Enfoque en Capas

Usa LocalStack como una capa en una estrategia de testing completa:

┌─────────────────────────────────────────────┐
│  Produccion (monitoreo, canary deployments)  │
├─────────────────────────────────────────────┤
│  Staging (AWS real, tests pre-produccion)    │
├─────────────────────────────────────────────┤
│  Integracion (AWS real, CI en rama main)     │
├─────────────────────────────────────────────┤
│  LocalStack (feedback rapido, todos los PRs) │
├─────────────────────────────────────────────┤
│  Tests unitarios (sin infraestructura)       │
└─────────────────────────────────────────────┘

Corre tests LocalStack en cada PR. Corre tests AWS reales en merge a main. Deploya a staging para validacion final.

Enfoques Asistidos por IA

La configuracion y mocking de LocalStack puede ser compleja. Las herramientas de IA ayudan.

Lo que la IA hace bien:

  • Generar configuraciones de endpoint LocalStack para providers Terraform
  • Convertir codigo de test AWS real a versiones compatibles con LocalStack
  • Crear fixtures moto para tests Python
  • Troubleshootear problemas de compatibilidad de servicios LocalStack

Lo que aun necesita humanos:

  • Decidir que tests necesitan AWS real vs LocalStack
  • Entender que limitaciones de LocalStack afectan tu caso de uso
  • Disenar arquitectura de test a traves de la piramide de testing
  • Validar que tests LocalStack realmente capturan issues reales

Prompt util:

Tengo este modulo Terraform que crea:

- S3 bucket con versionado y encriptacion
- Tabla DynamoDB con GSI
- Funcion Lambda disparada por eventos S3

Genera:

1. docker-compose.yml para LocalStack con servicios requeridos
2. Configuracion de provider Terraform para LocalStack
3. Codigo Terratest Go para validar el setup
4. Lista de limitaciones de las que debo estar consciente

Cuando Esto Falla

El testing LocalStack tiene limitaciones:

Gaps de servicios: Si usas AppSync, Neptune u otros servicios menos comunes, LocalStack podria no soportarlos.

Diferencias de comportamiento: Tests pasan localmente pero fallan en AWS. Esto ocurre cuando la emulacion de LocalStack difiere del comportamiento AWS.

Complejidad de estado: Instancias LocalStack de larga duracion acumulan estado. Tests se vuelven dependientes del orden.

Overhead de Docker: En runners CI con recursos limitados, LocalStack puede ser lento para iniciar o consumir demasiada memoria.

Considera enfoques complementarios:

Framework de Decision

Usa LocalStack cuando:

  • Iteracion rapida durante desarrollo
  • Costos CI son una preocupacion
  • Testeando servicios comunes (S3, DynamoDB, Lambda, SQS)
  • Aislamiento de red es requerido (testing offline)

Usa AWS real cuando:

  • Testeando policies IAM y permisos
  • Validando configuraciones de red
  • Usando servicios que LocalStack no soporta
  • Testing de integracion final antes de produccion

Usa moto cuando:

  • Unit testing codigo Python con llamadas AWS SDK
  • Velocidad es critica (moto es mas rapido que LocalStack)
  • No necesitas testing de Terraform/infraestructura

Midiendo el Exito

MetricaAntesDespuesComo Rastrear
Tiempo ejecucion de test8+ minutos<2 minutosMetricas CI
Costos AWS CI mensuales$500+<$100AWS Cost Explorer
Tests omitidos “muy lentos”Muchos0Reportes de cobertura
Fallos LocalStack vs AWSN/A<5%Comparacion resultados

Senales de alarma de que no funciona:

  • Tests pasan en LocalStack pero fallan en AWS real
  • LocalStack volviendose cuello de botella en CI
  • Desarrolladores bypaseando tests porque LocalStack es “suficiente”
  • Gaps de servicios forzando demasiados tests AWS reales

Que Sigue

Empieza con tus servicios mas testeados:

  1. Identifica que servicios AWS usan tus modulos Terraform
  2. Verifica compatibilidad LocalStack para esos servicios
  3. Configura docker-compose para desarrollo local
  4. Convierte una suite de test para usar LocalStack
  5. Mide mejora de velocidad e itera
  6. Agrega tests AWS reales como capa de integracion

El objetivo es feedback mas rapido, no reemplazar todo el testing AWS. LocalStack es una herramienta para velocidad; AWS real es la fuente de verdad.


Articulos relacionados:

Recursos externos:

Recursos Oficiales

See Also