floci: cualquier nube, en tu máquina
Emuladores locales de AWS, Azure y GCP en un contenedor por nube — MIT, gratis y sin cuenta. Qué es floci, para qué sirve, cómo levantar cada una, y qué no esperar de un emulador.
Querés probar el flujo que sube un archivo a un blob y dispara una cola. Tenés dos caminos, y los dos son incómodos.
Uno: apuntás a infra real. Ya sabemos cómo termina eso — a un guard de disparar producción — y encima pagás por uso mientras desarrollás.
El otro: mockeás el SDK entero. Y ahí el test deja de verificar tu código y empieza a verificar tu propio mock, que es el mismo problema del que escribí cuando hablamos de envolver EF Core en repositorios: una abstracción que simulás a mano es siempre menos fiel que la cosa real.
Hay un tercer camino, y es el que me interesa acá: correr la nube en tu máquina.
#Qué es
floci es una familia de emuladores locales de nube. Un contenedor por proveedor, un puerto por proveedor, y adentro los servicios que ese proveedor expone.
Cuatro nubes cubiertas hoy: AWS, Azure, GCP y OCI (Oracle Cloud).
Lo que lo distingue de otras cosas que probablemente ya usaste:
- No pide cuenta ni token. No hay login, no hay credenciales que gestionar. Le pasás claves descartables y anda.
- Está compilado a binario nativo (Quarkus con GraalVM Mandrel), así que no hay warmup de JVM: arranca en ~24 ms y se queda en ~13 MiB en idle.
- Todo en un puerto por nube. No es un emulador por servicio con su puerto propio — es un solo endpoint que rutea todo.
- El de AWS es drop-in replacement de LocalStack. Mismo puerto 4566, sin cambios de código. Si ya tenés algo apuntando ahí, cambia la imagen y listo.
- Motores reales, no mocks. Y esto es lo que más me importa: Lambda corre en contenedores Docker de verdad, RDS usa PostgreSQL o MySQL de verdad, ElastiCache levanta un Redis de verdad. No es un stub que devuelve JSON prefabricado.
- Sin telemetría.
Los datos de esta nota los saqué del sitio del proyecto y de sus repos oficiales en GitHub. Los números de servicios soportados se mueven con cada release, así que tomalos como referencia del momento en que escribí esto.
#Para qué sirve
Concretamente:
Tests de integración de verdad. Tu test habla el protocolo real del servicio a través del SDK real. No estás verificando que tu mock devuelva lo que le dijiste que devuelva.
CI sin credenciales. El pipeline levanta el contenedor, corre los tests y lo tira. No hay secretos de cloud en el runner, ni una cuenta de servicio que alguien tenga que rotar. Y con 24 ms de arranque, no le suma tiempo al build.
Desarrollar sin pagar por uso. Ese loop de "cambio una línea, subo, pruebo" no le cuesta nada a nadie. Y si querés, offline.
Probar infraestructura como código antes de aplicarla. Este es el que menos esperaba y más me gustó: aplicás el Terraform, el OpenTofu o el CloudFormation contra localhost primero. Los typos y los refactors mal hechos aparecen ahí, no en una cuenta real.
No tocar infra real. Si el entorno local no tiene forma de alcanzar producción, no hay nada que puedas disparar por accidente.
Enseñar sin riesgo de facturación. Para un workshop o para onboarding: cada persona levanta el stack completo en su máquina, sin cuentas que aprovisionar ni nada que limpiar después.
Y hay un caso de uso que el proyecto pone al frente y vale la pena notar: agentes de IA escribiendo código. Un agente que itera solo necesita un entorno que arranque rápido, no le cobre por intento y no pueda romper nada afuera. Además no hay credenciales reales dando vueltas en su contexto — nada que filtrar, nada que facturar. Un emulador local es exactamente eso.
#Licencia y precio
Como fueron tres preguntas separadas, van tres respuestas separadas:
Licencia: MIT. Permisiva. Podés usarlo en un proyecto comercial, cerrado, lo que sea, sin obligación de abrir tu código.
¿Open source? Sí, código abierto en GitHub — el runtime, la CLI, la UI y los módulos de Testcontainers, todo en la misma organización.
¿Free o freemium? Free, y esta es la parte que conviene leer con atención, porque no es lo mismo. En un modelo freemium hay features detrás de un plan pago, y te enterás justo cuando necesitás una: el servicio que te falta está en el tier de arriba.
Y el proyecto es bastante explícito sobre de dónde viene esa postura: LocalStack empezó a requerir auth token en marzo de 2026, y floci dice, con esas palabras, que nunca lo va a pedir. Sin "community edition" que se discontinúa, sin feature flags de enterprise.
Es la diferencia entre "gratis hasta que te haga falta algo" y "gratis". Ojo, que sea gratis no significa que no tenga costo: el costo es que un emulador es una aproximación, y de eso hablo al final.
#AWS — puerto 4566
El emulador principal, y el más completo: 69 servicios al momento de escribir esto. S3, DynamoDB, SQS, SNS, Lambda, IAM, KMS, Secrets Manager, RDS, ElastiCache, ECS, EKS, EventBridge, Step Functions, CloudFormation, API Gateway, Athena, Glue.
El puerto no es casualidad: es el mismo que usa LocalStack, justamente para que puedas cambiar sin tocar código.
docker run -d --name floci \
-p 4566:4566 \
-v /var/run/docker.sock:/var/run/docker.sock \
floci/floci:latest
Y apuntás el SDK con variables de entorno:
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
Si instalás la CLI, eso se reduce a dos líneas:
floci start
eval $(floci env)
#Azure — puerto 4577
24 servicios: Blob, Queue, Table, Functions, App Configuration, Key Vault, Event Hubs, Service Bus, Event Grid, Cosmos DB, Managed Identity, Entra ID.
docker run -d --name floci-az \
-p 4577:4577 \
-v /var/run/docker.sock:/var/run/docker.sock \
floci/floci-az:latest
Lo lindo acá: el connection string de development storage funciona sin tocar nada, así que el código que ya escribiste contra Azurite sigue andando igual.
DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMh0==;BlobEndpoint=http://localhost:4577/devstoreaccount1;QueueEndpoint=http://localhost:4577/devstoreaccount1-queue;TableEndpoint=http://localhost:4577/devstoreaccount1-table;
La comparación con Azurite es la que más importa si venís del mundo .NET: Azurite emula storage y nada más — Blob, Queue, Table, cada uno en su puerto. Acá tenés esos tres más Functions, Cosmos, Key Vault y Service Bus, todos en 4577. Event Hubs además levanta AMQP en 5672, y Kafka en 9093 si lo habilitás.
Un detalle práctico: por default todo va por HTTP plano. Si necesitás el SDK de Cosmos para Java, o Terraform/OpenTofu, hay que habilitar TLS con FLOCI_AZ_TLS_ENABLED=true — genera un certificado self-signed al arrancar.
Con la CLI:
floci az start
eval $(floci az env)
#GCP — puerto 4588
24 servicios: Pub/Sub, Firestore, Datastore, Cloud Storage, Secret Manager, Cloud KMS, IAM, Cloud Run, Cloud Functions, GKE, Cloud SQL, Cloud Tasks, Cloud Scheduler, Eventarc, Firebase Auth.
docker run -d --name floci-gcp \
-p 4588:4588 \
-v /var/run/docker.sock:/var/run/docker.sock \
floci/floci-gcp:latest
Acá la ganancia es distinta a las otras dos. Google ya daba emuladores locales, pero uno por servicio, cada uno con su puerto y su forma de arrancar. Esto los unifica: todos los *_EMULATOR_HOST apuntan al mismo lugar.
export PUBSUB_EMULATOR_HOST=localhost:4588
export FIRESTORE_EMULATOR_HOST=localhost:4588
export DATASTORE_EMULATOR_HOST=localhost:4588
export STORAGE_EMULATOR_HOST=http://localhost:4588
export SECRET_MANAGER_EMULATOR_HOST=localhost:4588
export GOOGLE_CLOUD_PROJECT=floci-local
Con la CLI: floci gcp start y eval $(floci gcp env).
(Y si te toca Oracle Cloud: floci oci start, puerto 4599, 7 servicios.)
#Desde .NET: Testcontainers
Esta es la parte que más me sirve a mí, y la razón por la que le presté atención al proyecto: hay un módulo de Testcontainers para .NET, así que el contenedor lo levanta el test, no vos.
dotnet add package Testcontainers.Floci
public sealed class OrderTests : IAsyncLifetime
{
private readonly FlociContainer _floci = new FlociBuilder("floci/floci:latest")
.WithRegion("eu-west-2")
.WithSqs(new SqsConfig { VisibilityTimeout = 60 })
.Build();
public Task InitializeAsync() => _floci.StartAsync();
public Task DisposeAsync() => _floci.DisposeAsync().AsTask();
[Fact]
public async Task SendsAndReceives()
{
using var sqs = new AmazonSQSClient(
_floci.AccessKey,
_floci.SecretKey,
new AmazonSQSConfig
{
ServiceURL = _floci.GetEndpoint(),
AuthenticationRegion = _floci.Region,
});
var queueUrl = (await sqs.CreateQueueAsync("orders")).QueueUrl;
await sqs.SendMessageAsync(queueUrl, "hello from floci");
var received = await sqs.ReceiveMessageAsync(queueUrl);
Assert.Single(received.Messages);
}
}
Fijate que el cliente es el AmazonSQSClient de verdad, con el SDK de verdad. Lo único que cambia es el ServiceURL. Eso es exactamente lo contrario de mockear: el test ejercita el mismo código que va a correr en producción.
En un proyecto real, pineá la versión de la imagen en lugar de usar latest — un test que cambia de comportamiento porque salió una release nueva es un test que no te sirve.
Hay módulos equivalentes para Java, Node, Python y Go.
#La consola local
Hay un extra que no esperaba: floci-ui, una consola visual para los emuladores, al estilo de la consola de AWS. Browser de S3, DynamoDB, colas de SQS, logs de Lambda, Blob y Queue de Azure — todo desde un mismo lugar.
Es la pieza que suele faltar cuando trabajás contra un emulador: poder mirar qué quedó realmente guardado sin escribir un script para consultarlo.
#Qué no es
Esta sección es la que le falta a casi todo lo que se escribe sobre herramientas nuevas, así que va primero lo importante: un emulador no es la nube.
- Es una aproximación, no el servicio. Los propios repos hablan de "servicios con forma de GCP", y me parece la descripción más honesta que podrían haber elegido. Que el SDK no se queje no garantiza que el comportamiento sea idéntico en el borde: límites, cuotas, consistencia eventual, mensajes de error exactos.
- La fidelidad "100%" es un claim del proyecto, no una verificación independiente. Es de ellos, no mía. Verificalo contra los servicios puntuales que vos usás, no contra el número global.
- No valida credenciales. Eso es parte de la comodidad, pero tiene una consecuencia directa: no podés usarlo para testear que tus políticas de IAM estén bien escritas. Un test que pasa acá no dice nada sobre si en producción ese rol tiene permiso.
- Necesita el socket de Docker. Es la contracara de que los motores sean reales: si Lambda corre en un contenedor y RDS levanta un PostgreSQL, alguien tiene que poder crear esos contenedores. Si tu CI corre en un entorno sin acceso al socket, ahí tenés un problema a resolver.
- Es un proyecto joven. Con lo que eso implica: se mueve rápido, y no tiene los años de casos raros encontrados que tienen las alternativas establecidas.
Ninguna de estas cosas lo descalifica. Lo ubican: sirve para el loop de desarrollo y para tests de integración, no para validar que tu infra de producción está bien configurada. Eso último se sigue verificando en un entorno real.
#Lo que me parece que importa
Que sea rápido y liviano está bien, pero no es lo que me llamó la atención.
Lo que me llamó la atención es que no haya un tier de arriba. Cuando la lista de servicios está completa desde el minuto cero, la decisión de qué usar la tomás por criterio técnico y no por lo que te dejan tocar sin pagar. Es una diferencia chica en el discurso y grande en la práctica — y el token de LocalStack en marzo es el recordatorio de por qué importa.
Y la parte que más me gusta no tiene nada que ver con la plata: es que mientras desarrollás, no hay ningún camino desde tu máquina hasta producción. No porque te hayas acordado de chequear el entorno, sino porque literalmente no está conectado.
Eso es lo mismo que hace un guard, pero por arquitectura en lugar de por memoria.
#floci #cloud #testing #dotnet #docker