floci: Any Cloud, on Your Machine
Local AWS, Azure, and GCP emulators — one container per cloud, MIT licensed, free, no account required. What floci is, what it's for, how to start each one, and what not to expect from an emulator.
You want to test the flow that uploads a file to a blob and triggers a queue. You've got two options, and both are uncomfortable.
One: point at real infrastructure. We already know how that ends — one guard away from prod — and you're paying per use while you develop.
The other: mock the entire SDK. At which point the test stops verifying your code and starts verifying your own mock, which is the same problem I wrote about when covering wrapping EF Core in repositories: an abstraction you fake by hand is always less faithful than the real thing.
There's a third option, and that's the one I care about here: run the cloud on your machine.
#What it is
floci is a family of local cloud emulators. One container per provider, one port per provider, and inside it the services that provider exposes.
Four clouds covered today: AWS, Azure, GCP, and OCI (Oracle Cloud).
What sets it apart from things you've probably already used:
- No account, no token. There's no login and no credentials to manage. You hand it throwaway keys and it works.
- It's compiled to a native binary (Quarkus with GraalVM Mandrel), so there's no JVM warmup: it starts in ~24 ms and sits at ~13 MiB idle.
- Everything on one port per cloud. It isn't one emulator per service with its own port — it's a single endpoint that routes everything.
- The AWS one is a drop-in replacement for LocalStack. Same port 4566, no code changes. If you already have something pointing there, swap the image and you're done.
- Real engines, not mocks. This is the part I care about most: Lambda runs in actual Docker containers, RDS uses actual PostgreSQL or MySQL, ElastiCache brings up an actual Redis. It isn't a stub returning canned JSON.
- No telemetry.
The facts in this post come from the project site and its official repos on GitHub. Supported-service counts move with every release, so treat them as a snapshot from when I wrote this.
#What it's for
Concretely:
Real integration tests. Your test speaks the service's actual protocol through the actual SDK. You aren't verifying that your mock returns what you told it to return.
CI without credentials. The pipeline starts the container, runs the tests, and throws it away. No cloud secrets on the runner, and no service account for someone to rotate. And at 24 ms startup, it adds nothing to the build.
Developing without paying per use. That "change one line, deploy, check" loop costs nobody anything. And offline, if you want.
Dry-running infrastructure as code. This is the one I least expected and liked most: apply your Terraform, OpenTofu, or CloudFormation against localhost first. Typos and bad refactors surface there, not in a real account.
Not touching real infrastructure. If the local environment has no way to reach production, there's nothing you can trigger by accident.
Teaching without billing risk. For a workshop or for onboarding: everyone brings up the whole stack on their own machine, with no accounts to provision and nothing to clean up afterward.
And there's one use case the project puts front and center that's worth noting: AI agents writing code. An agent iterating on its own needs an environment that starts fast, doesn't bill per attempt, and can't break anything outside itself. On top of that, there are no real credentials floating around in its context — nothing to exfiltrate, nothing to bill. A local emulator is exactly that.
#License and price
Since those were three separate questions, here are three separate answers:
License: MIT. Permissive. You can use it in a commercial or closed-source project with no obligation to open your own code.
Open source? Yes, on GitHub — the runtime, the CLI, the UI, and the Testcontainers modules, all in the same organization.
Free or freemium? Free, and this distinction is worth reading carefully, because they're not the same thing. In a freemium model some features sit behind a paid plan, and you find out exactly when you need one: the service you're missing is in the tier above.
The project is fairly explicit about where that stance comes from: LocalStack started requiring an auth token in March 2026, and floci says, in those words, that it never will. No "community edition" sunset, no enterprise feature flags.
It's the difference between "free until you need something" and "free." That said, free doesn't mean costless: the cost is that an emulator is an approximation, and I get to that at the end.
#AWS — port 4566
The main emulator, and the most complete one: 69 services as of this writing. S3, DynamoDB, SQS, SNS, Lambda, IAM, KMS, Secrets Manager, RDS, ElastiCache, ECS, EKS, EventBridge, Step Functions, CloudFormation, API Gateway, Athena, Glue.
The port isn't a coincidence: it's the same one LocalStack uses, precisely so you can switch without touching code.
docker run -d --name floci \
-p 4566:4566 \
-v /var/run/docker.sock:/var/run/docker.sock \
floci/floci:latest
Then you point the SDK at it with environment variables:
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
If you install the CLI, that collapses into two lines:
floci start
eval $(floci env)
#Azure — port 4577
24 services: 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
The nice part here: the development storage connection string works unchanged, so code you already wrote against Azurite keeps working as-is.
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;
The comparison against Azurite is the one that matters most if you come from .NET: Azurite emulates storage and nothing else — Blob, Queue, Table, each on its own port. Here you get those three plus Functions, Cosmos, Key Vault, and Service Bus, all on 4577. Event Hubs also brings up AMQP on 5672, and Kafka on 9093 if you enable it.
One practical detail: by default everything runs over plain HTTP. If you need the Java Cosmos SDK, or Terraform/OpenTofu, you have to enable TLS with FLOCI_AZ_TLS_ENABLED=true — it generates a self-signed certificate at startup.
With the CLI:
floci az start
eval $(floci az env)
#GCP — port 4588
24 services: 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
The win here is different from the other two. Google already shipped local emulators, but one per service, each with its own port and its own way of starting. This unifies them: every *_EMULATOR_HOST points at the same place.
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
With the CLI: floci gcp start and eval $(floci gcp env).
(And if you're on Oracle Cloud: floci oci start, port 4599, 7 services.)
#From .NET: Testcontainers
This is the part that's most useful to me, and the reason I paid attention to the project at all: there's a Testcontainers module for .NET, so the test starts the container, not you.
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);
}
}
Notice the client is the real AmazonSQSClient, with the real SDK. The only thing that changes is ServiceURL. That's the exact opposite of mocking: the test exercises the same code that will run in production.
In a real project, pin the image version instead of using latest — a test whose behavior changes because a new release shipped is a test that isn't doing its job.
There are equivalent modules for Java, Node, Python, and Go.
#The local console
There's one extra I wasn't expecting: floci-ui, a visual console for the emulators, in the style of the AWS Console. An S3 browser, DynamoDB, SQS queues, Lambda logs, Azure Blob and Queue — all from one place.
It's the piece that's usually missing when you work against an emulator: being able to look at what actually got stored without writing a script to query it.
#What it isn't
This is the section missing from most writing about new tools, so the important part goes first: an emulator is not the cloud.
- It's an approximation, not the service. The repos themselves describe them as "GCP-shaped services," which strikes me as the most honest wording they could have picked. The SDK not complaining doesn't guarantee identical behavior at the edges: limits, quotas, eventual consistency, exact error messages.
- The "100%" fidelity figure is the project's claim, not an independent verification. It's theirs, not mine. Check it against the specific services you actually use, not against the global number.
- It doesn't validate credentials. That's part of the convenience, but it has a direct consequence: you can't use it to test whether your IAM policies are written correctly. A test passing here says nothing about whether that role has permission in production.
- It needs the Docker socket. That's the flip side of the engines being real: if Lambda runs in a container and RDS brings up a PostgreSQL, something has to be able to create those containers. If your CI runs somewhere without socket access, that's a problem you'll have to solve.
- It's a young project. With everything that implies: it moves fast, and it doesn't have the years of accumulated edge cases the established alternatives do.
None of this disqualifies it. It places it: it's for the development loop and for integration tests, not for validating that your production infrastructure is configured correctly. That last part still gets verified in a real environment.
#What I think actually matters
Fast and lightweight is nice, but it isn't what caught my attention.
What caught my attention is that there's no tier above. When the service list is complete from minute zero, you decide what to use on technical grounds instead of on what you're allowed to touch without paying. It's a small difference in the pitch and a large one in practice — and LocalStack's token in March is the reminder of why it matters.
And the part I like best has nothing to do with money: while you're developing, there's no path at all from your machine to production. Not because you remembered to check the environment, but because it literally isn't connected.
That's the same thing a guard does, except by architecture instead of by memory.
#floci #cloud #testing #dotnet #docker