Módulo 4: LocalStack — AWS Local Development
2. LocalStack Setup y Configuración
Descripción
En esta cápsula vas a instalar y configurar LocalStack como un servicio dentro de tu Docker Compose — el mismo Compose que construiste en el Módulo 2 con FastAPI y Redis. Al terminar, tendrás LocalStack corriendo junto a tu app AI, listo para emular S3, Lambda, y otros servicios AWS. No es una instalación aislada: LocalStack se integra en tu infraestructura de desarrollo existente.
Contexto: La cápsula anterior explicó qué es LocalStack y por qué importa. Aquí lo pones en marcha. El setup es deliberadamente simple: 5 líneas en tu Compose file y un comando para verificar. El valor no está en instalarlo — está en lo que haces con él en las cápsulas siguientes.
LocalStack en Docker Compose
La configuración mínima
LocalStack corre como un container Docker. La forma más limpia de integrarlo es como un servicio más en tu Docker Compose:
# docker-compose.yml — Agregar LocalStack al Compose del M2
services:
# --- Servicios existentes del M2 ---
api:
build:
context: ./api
ports:
- "8000:8000"
env_file:
- .env
environment:
- REDIS_URL=redis://cache:6379
- AWS_ENDPOINT_URL=http://localstack:4566
depends_on:
cache:
condition: service_healthy
localstack:
condition: service_healthy
cache:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# --- NUEVO: LocalStack ---
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=s3,lambda
- DEBUG=0
- LAMBDA_EXECUTOR=docker
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- localstack_data:/var/lib/localstack
- /var/run/docker.sock:/var/run/docker.sock
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
redis_data:
localstack_data:
Cada línea explicada
localstack:
image: localstack/localstack:latest
# Imagen oficial de LocalStack desde Docker Hub
ports:
- "4566:4566"
# Puerto único para TODOS los servicios AWS
# S3, Lambda, API Gateway — todo por el puerto 4566
# LocalStack rutea internamente según el servicio solicitado
environment:
- SERVICES=s3,lambda
# Solo arranca los servicios que necesitas
# Menos servicios = arranque más rápido, menos RAM
# Para esta guía: S3 y Lambda son suficientes
- DEBUG=0
# 0 = logs normales, 1 = logs verbose
# Usa 1 cuando debuggees problemas
- LAMBDA_EXECUTOR=docker
# Lambda ejecuta funciones en containers Docker separados
# Alternativa: "local" (ejecuta en el mismo proceso)
# "docker" es más fiel a AWS real
- DOCKER_HOST=unix:///var/run/docker.sock
# Permite a LocalStack crear containers para Lambda
# Necesario cuando LAMBDA_EXECUTOR=docker
volumes:
- localstack_data:/var/lib/localstack
# Persiste datos entre reinicios del container
# Sin esto, pierdes buckets y funciones al reiniciar
- /var/run/docker.sock:/var/run/docker.sock
# Da acceso a Docker socket para crear containers Lambda
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
# Verifica que LocalStack está ready
# Los servicios no están disponibles hasta que el health pasa
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
# start_period: LocalStack tarda ~10-15s en arrancar
Por qué un solo puerto
A diferencia de AWS real (donde S3 está en s3.amazonaws.com, Lambda en lambda.us-east-1.amazonaws.com), LocalStack expone todo por un solo puerto: 4566. El servicio se determina por los headers de la request, no por la URL.
AWS real:
├── s3.amazonaws.com → S3
├── lambda.us-east-1.amazonaws.com → Lambda
└── apigateway.us-east-1.amazonaws.com → API Gateway
LocalStack:
└── localhost:4566 → Todo
├── boto3 envía header con servicio solicitado
└── LocalStack rutea internamente
Esto simplifica tu setup: un puerto, un container, todos los servicios.
Configuración del CLI
awslocal: tu nuevo mejor amigo
awslocal es un wrapper del AWS CLI que automáticamente agrega --endpoint-url=http://localhost:4566:
# Instalar
pip install awscli-local
# Verificar
awslocal --version
Comparación:
# Sin awslocal (verbose, fácil olvidar el flag)
aws --endpoint-url=http://localhost:4566 s3 ls
aws --endpoint-url=http://localhost:4566 lambda list-functions
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
# Con awslocal (limpio, mismo resultado)
awslocal s3 ls
awslocal lambda list-functions
awslocal s3 mb s3://my-bucket
Configurar credenciales dummy
LocalStack no valida credenciales (Community Edition), pero el AWS CLI necesita que existan. Configura credenciales dummy:
# Configura credenciales fake (LocalStack las ignora)
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region us-east-1
# Verifica
aws configure list
# Debe mostrar access_key y secret_key como "test"
Estas credenciales nunca llegan a AWS real — solo satisfacen el requisito del CLI. LocalStack acepta cualquier valor.
Variables de entorno alternativas
Si prefieres no modificar tu ~/.aws/credentials, usa variables de entorno:
# En tu .env o en tu shell
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
export AWS_ENDPOINT_URL=http://localhost:4566
Con AWS_ENDPOINT_URL configurado, incluso el aws CLI normal apunta a LocalStack sin necesitar awslocal:
# Con AWS_ENDPOINT_URL configurado:
aws s3 ls # ← esto ya apunta a LocalStack
Levantar y Verificar
Primer arranque
# Levanta todo (desde el directorio de tu proyecto)
docker compose up -d
# Verifica que todos los servicios están up
docker compose ps
# Output esperado:
# NAME STATUS PORTS
# api Up (healthy) 0.0.0.0:8000->8000/tcp
# cache Up (healthy) 6379/tcp
# localstack Up (healthy) 0.0.0.0:4566->4566/tcp
Verificar servicios disponibles
# Health check de LocalStack
curl http://localhost:4566/_localstack/health | python3 -m json.tool
# Output esperado:
# {
# "services": {
# "s3": "available",
# "lambda": "available"
# },
# "version": "3.x.x"
# }
Verificar con awslocal
# S3: listar buckets (vacío al inicio)
awslocal s3 ls
# (sin output — no hay buckets aún)
# Lambda: listar funciones (vacío al inicio)
awslocal lambda list-functions
# {"Functions": []}
# Crear un bucket de prueba
awslocal s3 mb s3://test-bucket
# make_bucket: test-bucket
# Verificar que existe
awslocal s3 ls
# 2026-03-08 12:00:00 test-bucket
# Limpiar
awslocal s3 rb s3://test-bucket
Verificar con Python (boto3)
# test_localstack.py
import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566",
aws_access_key_id="test",
aws_secret_access_key="test",
region_name="us-east-1",
)
# Crear bucket
s3.create_bucket(Bucket="test-from-python")
# Listar buckets
response = s3.list_buckets()
for bucket in response["Buckets"]:
print(f"Bucket: {bucket['Name']}")
# Output esperado:
# Bucket: test-from-python
# Limpiar
s3.delete_bucket(Bucket="test-from-python")
print("LocalStack + boto3 funcionando correctamente")
python test_localstack.py
# Bucket: test-from-python
# LocalStack + boto3 funcionando correctamente
Si ves ese output, tu setup está completo.
Configuraciones Avanzadas
SERVICES: controla qué se arranca
# Solo S3 y Lambda (lo que necesitamos)
environment:
- SERVICES=s3,lambda
# Todo (más lento al arrancar, más RAM)
environment:
- SERVICES= # vacío = todos
# S3, Lambda, SQS, DynamoDB
environment:
- SERVICES=s3,lambda,sqs,dynamodb
Regla: solo lista los servicios que usas. Cada servicio adicional consume RAM y ralentiza el arranque.
LAMBDA_EXECUTOR: cómo ejecuta funciones
# docker (recomendado) — cada función en su container
environment:
- LAMBDA_EXECUTOR=docker
# local — ejecuta en el proceso de LocalStack (más rápido, menos fiel)
environment:
- LAMBDA_EXECUTOR=local
docker es más fiel a AWS real pero requiere acceso al Docker socket. local es más rápido para iteración pero puede tener diferencias de comportamiento.
Persistence con volúmenes
# Sin volumen: datos se pierden al reiniciar
localstack:
image: localstack/localstack:latest
# Con volumen: datos persisten
localstack:
image: localstack/localstack:latest
volumes:
- localstack_data:/var/lib/localstack
En Community Edition, la persistence es limitada. Para garantizar que tu entorno está listo, usa un script de inicialización:
#!/bin/bash
# scripts/setup-localstack.sh
echo "Esperando a que LocalStack esté ready..."
until curl -s http://localhost:4566/_localstack/health | grep -q '"s3": "available"'; do
sleep 2
done
echo "LocalStack ready"
echo "Creando buckets..."
awslocal s3 mb s3://ai-input
awslocal s3 mb s3://ai-output
echo "Verificando..."
awslocal s3 ls
echo "Setup completo"
Init hooks: setup automático al arrancar
LocalStack soporta scripts de inicialización que se ejecutan al arrancar:
localstack:
image: localstack/localstack:latest
volumes:
- ./init-scripts:/etc/localstack/init/ready.d
# init-scripts/setup.sh
#!/bin/bash
awslocal s3 mb s3://ai-input
awslocal s3 mb s3://ai-output
echo "Buckets creados automáticamente"
Cualquier script en /etc/localstack/init/ready.d/ se ejecuta cuando LocalStack está listo. Esto elimina la necesidad de correr el setup manualmente.
LocalStack Dentro de la Red Docker
Comunicación entre servicios
Dentro de Docker Compose, tu app FastAPI puede acceder a LocalStack usando el nombre del servicio como hostname:
# Desde tu FastAPI (dentro del Compose)
s3 = boto3.client(
"s3",
endpoint_url="http://localstack:4566", # hostname del servicio
aws_access_key_id="test",
aws_secret_access_key="test",
)
# Desde tu máquina (fuera del Compose)
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566", # puerto expuesto
aws_access_key_id="test",
aws_secret_access_key="test",
)
Dentro de Docker Compose:
api → http://localstack:4566 (red interna)
api → http://cache:6379 (red interna)
Desde tu máquina:
tú → http://localhost:4566 (puerto expuesto)
tú → http://localhost:8000 (puerto expuesto)
El .env actualizado
# .env — Agrega las variables de LocalStack
OPENAI_API_KEY=sk-proj-your-key-here
ENVIRONMENT=development
LOG_LEVEL=debug
CACHE_TTL=3600
MODEL_NAME=gpt-4o-mini
MAX_TOKENS=500
# LocalStack
AWS_ENDPOINT_URL=http://localstack:4566
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test
AWS_DEFAULT_REGION=us-east-1
Ejercicios
Ejercicio 1: Compose con LocalStack desde cero
Crea un docker-compose.yml que tenga solo LocalStack con S3 y Lambda habilitados. Levántalo, verifica el health check, crea un bucket, y lista los buckets con awslocal.
Ver solución
# docker-compose.yml
services:
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=s3,lambda
- DEBUG=0
volumes:
- localstack_data:/var/lib/localstack
- /var/run/docker.sock:/var/run/docker.sock
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
localstack_data:
# Levantar
docker compose up -d
# Esperar health check
docker compose ps
# Esperar hasta que localstack muestre "Up (healthy)"
# Verificar health
curl http://localhost:4566/_localstack/health
# Crear bucket
awslocal s3 mb s3://mi-primer-bucket
# Listar buckets
awslocal s3 ls
# Output: 2026-03-08 ... mi-primer-bucket
Ejercicio 2: Script de inicialización automática
Crea un init script que se ejecute automáticamente cuando LocalStack arranca. El script debe crear dos buckets (ai-models y ai-results) y confirmar con un listado.
Ver solución
# init-scripts/setup.sh
#!/bin/bash
set -e
echo "=== Inicializando LocalStack ==="
awslocal s3 mb s3://ai-models
echo "Bucket ai-models creado"
awslocal s3 mb s3://ai-results
echo "Bucket ai-results creado"
echo "=== Buckets disponibles ==="
awslocal s3 ls
echo "=== Inicialización completa ==="
# docker-compose.yml — agregar volumen de init
services:
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=s3,lambda
volumes:
- localstack_data:/var/lib/localstack
- /var/run/docker.sock:/var/run/docker.sock
- ./init-scripts:/etc/localstack/init/ready.d
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
localstack_data:
# Hacer el script ejecutable
chmod +x init-scripts/setup.sh
# Levantar
docker compose up -d
# Verificar logs para ver la inicialización
docker compose logs localstack | grep "Bucket"
# Verificar buckets
awslocal s3 ls
# ai-models
# ai-results
Ejercicio 3: Verificación con boto3
Escribe un script Python que conecte a LocalStack, cree un bucket, suba un archivo JSON con metadata de un modelo AI, lo descargue, y verifique que el contenido es idéntico.
Ver solución
# verify_localstack.py
import json
import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566",
aws_access_key_id="test",
aws_secret_access_key="test",
region_name="us-east-1",
)
bucket = "verification-test"
key = "models/gpt4o-mini-config.json"
model_config = {
"model": "gpt-4o-mini",
"max_tokens": 500,
"temperature": 0.7,
"system_prompt": "Eres un asistente útil y conciso.",
"version": "2026-03",
}
print("1. Creando bucket...")
s3.create_bucket(Bucket=bucket)
print("2. Subiendo archivo...")
s3.put_object(
Bucket=bucket,
Key=key,
Body=json.dumps(model_config),
ContentType="application/json",
)
print("3. Descargando archivo...")
response = s3.get_object(Bucket=bucket, Key=key)
downloaded = json.loads(response["Body"].read().decode("utf-8"))
print("4. Verificando contenido...")
assert downloaded == model_config, "El contenido no coincide"
print("5. Listando objetos...")
objects = s3.list_objects_v2(Bucket=bucket)
for obj in objects.get("Contents", []):
print(f" {obj['Key']} ({obj['Size']} bytes)")
print("\nVerificación completa — LocalStack + boto3 funcionando")
python verify_localstack.py
# 1. Creando bucket...
# 2. Subiendo archivo...
# 3. Descargando archivo...
# 4. Verificando contenido...
# 5. Listando objetos...
# models/gpt4o-mini-config.json (123 bytes)
#
# Verificación completa — LocalStack + boto3 funcionando
Ejercicio 4: Compose multi-servicio completo
Extiende el Docker Compose del Módulo 2 (FastAPI + Redis) agregando LocalStack. Configura las variables de entorno para que FastAPI pueda conectarse a LocalStack usando el hostname del servicio. Verifica que los tres servicios están healthy.
Ver solución
# docker-compose.yml
services:
api:
build:
context: ./api
ports:
- "8000:8000"
env_file:
- .env
environment:
- REDIS_URL=redis://cache:6379
- AWS_ENDPOINT_URL=http://localstack:4566
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=us-east-1
depends_on:
cache:
condition: service_healthy
localstack:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
cache:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=s3,lambda
- DEBUG=0
- LAMBDA_EXECUTOR=docker
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- localstack_data:/var/lib/localstack
- /var/run/docker.sock:/var/run/docker.sock
- ./init-scripts:/etc/localstack/init/ready.d
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
redis_data:
localstack_data:
docker compose up -d
docker compose ps
# Verificar los tres servicios:
# api Up (healthy)
# cache Up (healthy)
# localstack Up (healthy)
curl http://localhost:8000/health
# {"status": "healthy", ...}
curl http://localhost:4566/_localstack/health
# {"services": {"s3": "available", "lambda": "available"}}
Troubleshooting
"LocalStack no arranca — container exits inmediatamente"
# Revisar logs
docker compose logs localstack
# Causa común: puerto 4566 ya en uso
lsof -i :4566
# Si hay otro proceso, mátalo o cambia el puerto en Compose
# Causa común: Docker socket no accesible
ls -la /var/run/docker.sock
# Debe existir y tener permisos de lectura
"curl a /_localstack/health retorna connection refused"
# LocalStack tarda ~10-15s en arrancar
# Espera y reintenta
sleep 15
curl http://localhost:4566/_localstack/health
# Si sigue fallando, verifica que el container está corriendo
docker compose ps localstack
docker compose logs localstack --tail 20
"awslocal s3 ls retorna error de credenciales"
# Configura credenciales dummy
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
aws configure set region us-east-1
# O usa variables de entorno
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
"LAMBDA_EXECUTOR=docker no funciona en Mac con Apple Silicon"
# En Mac con M1/M2/M3, asegúrate de tener Docker Desktop actualizado
docker --version
# Si Lambda falla con Docker executor, prueba local
environment:
- LAMBDA_EXECUTOR=local
# Menos fiel a AWS pero funciona en todas las plataformas
"El servicio X no aparece en health check"
# Verifica que lo incluiste en SERVICES
# Si SERVICES=s3,lambda y pides SQS, no estará disponible
curl http://localhost:4566/_localstack/health
# Agrega el servicio a la variable SERVICES y reinicia
Resumen
- LocalStack se integra en Docker Compose como un servicio más, al lado de tu app y Redis. No es una herramienta separada.
- Un solo puerto (4566) expone todos los servicios AWS emulados. Simples, limpio.
awslocales tu CLI para interactuar con LocalStack — same commands asaws, minus the--endpoint-url.- Credenciales dummy (
test/test) satisfacen el requisito del CLI. LocalStack Community no valida credenciales. - Init scripts en
/etc/localstack/init/ready.d/automatizan el setup (crear buckets, funciones) al arrancar. - Dentro del Compose, los servicios se comunican por hostname (
http://localstack:4566). Desde tu máquina, usashttp://localhost:4566. - SERVICES controla qué se arranca. Solo S3 y Lambda para esta guía — menos es más.
Recursos Adicionales
- LocalStack Docker Compose Setup — Guía oficial de instalación con Compose
- LocalStack Configuration — Todas las variables de configuración
- LocalStack Init Hooks — Scripts de inicialización automática
- awscli-local GitHub — Documentación de awslocal
- LocalStack Lambda Executor — Configuración de Lambda en LocalStack
- Docker Compose Networking — Cómo los servicios se comunican en Compose
- LocalStack Health Endpoint — Endpoints internos de LocalStack