Why I Swapped LocalStack for Floci — And Didn't Look Back

Why I Swapped LocalStack for Floci — And Didn't Look Back

LocalStack started gating CI behind auth tokens. Here's how I moved my daily AWS dev loop to Floci — faster boots, MIT license, no signup wall.


On this page

My CI broke on a Tuesday morning. Green for a year, red for no reason. I scrolled the logs — LOCALSTACK_AUTH_TOKEN missing. Right. The March 23 deadline had arrived and I’d forgotten about it.

Three hours later, I’d moved the whole stack off LocalStack and onto Floci. Same endpoints, same boto3 clients, no vendor account, less YAML. This is the field report from an engineer who just migrated a mid-size AWS project and lived to tell the tale.

The popcorn-cloud pitch

Floci is an AWS local emulator. Same niche as LocalStack — run S3, SQS, DynamoDB, Lambda, RDS against your laptop instead of a real AWS account. It binds to port 4566, which is the LocalStack port, so most of your existing code keeps working without edits.

The name comes from cirrocumulus floccus, those tiny popcorn-shaped clouds at sunset. The design leans into the metaphor: small, fast, no ceremony. It’s a Quarkus Native binary compiled with GraalVM, which mostly means it starts in the time you take to blink.

# One container, no account, no auth token, no sign-up wall
docker run -d --name floci \
  -p 4566:4566 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e FLOCI_DEFAULT_REGION=us-east-1 \
  hectorvent/floci:latest

That docker.sock mount is what lets Floci spawn real Lambda runtime containers. Leave it off and lambda.invoke() politely refuses to do anything useful.

Floci vs LocalStack at a glance

I’m not pretending this is a fair fight on coverage — LocalStack Ultimate supports 110+ services and Floci ships with 35. But on every other axis I care about day-to-day, the numbers tell a blunt story.

DimensionFlociLocalStack Community (2026)
Cold start~24 ms~3.3 s
Idle memory~13 MiB~143 MiB
Image size~90 MB~1.0 GB
Services35Hobby limited; paid 110+
Auth token for CINeverRequired since Mar 23, 2026
LicenseMIT, permanently freeHobby free non-commercial; Starter $39/mo
SDK tests passing1,850+ publishedInternal, not published

LocalStack’s Hobby tier is still free for individual engineers, but it prohibits commercial use and needs an account. That’s the part that bit me: CI runners need a real token now, tied to a real person, and that token behaves like a password you can’t rotate without breaking builds.

My daily dev loop, in one docker-compose

Here’s the file I drop into every new project. Nothing exotic — just the shape I’ve landed on after wasting too many evenings fighting container networking.

# docker-compose.yml — local AWS for the whole team
services:
  floci:
    image: hectorvent/floci:latest
    ports:
      - "4566:4566"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # Lambda needs this
      - ./.floci-data:/app/data                    # Persist S3/Dynamo between runs
    environment:
      FLOCI_DEFAULT_REGION: us-east-1
      FLOCI_STORAGE_MODE: hybrid
      FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK: bridge

The boring part of the SDK call stays exactly as it always was:

import boto3

# Dummy creds — Floci validates SigV4 but doesn't care about the secret itself
s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:4566",
    region_name="us-east-1",
    aws_access_key_id="test",
    aws_secret_access_key="test",
)

s3.create_bucket(Bucket="my-local-bucket")

Common pitfall I hit twice before learning: forgetting to .gitignore .floci-data/. DynamoDB writes pile up fast and your next PR turns into 400 files of binary noise.

Lambda that actually runs your code

LocalStack Community’s Lambda used to execute your handler in a cut-down process — good enough for unit-ish tests, not great for “does my Python 3.13 wheel actually load.” Floci’s Lambda spawns a real Docker container per invocation, which is exactly why you mount docker.sock.

# Package and deploy locally, same shape as real AWS
zip -r function.zip handler.py requirements/
aws --endpoint-url=http://localhost:4566 lambda create-function \
  --function-name hello \
  --runtime python3.13 \
  --role arn:aws:iam::000000000000:role/lambda-exec \
  --handler handler.lambda_handler \
  --zip-file fileb://function.zip

Because it’s a real container, cold starts are real too. My first reaction was annoyance; my second was “oh, I just caught a bug where the container couldn’t import lxml because of a glibc mismatch.” That bug would have shipped to prod unnoticed.

The classic mistake: running Floci inside its own Docker network and expecting Lambda containers to reach it. Set FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK=bridge or name your compose network explicitly. Otherwise your handler gets a very confused ConnectionRefusedError.

IAM that calls your bluff

This is the bit I didn’t expect to love. Floci does full SigV4 signature validation and honors IAM role assumption across Lambda, RDS, and ElastiCache. LocalStack Community never enforced IAM seriously — you could run the whole suite with root-equivalent access and your tests stayed green on policies that would explode in production.

# Assume a readonly role — actually validated, not rubber-stamped
sts = boto3.client("sts", endpoint_url="http://localhost:4566", ...)
creds = sts.assume_role(
    RoleArn="arn:aws:iam::000000000000:role/readonly",
    RoleSessionName="local-test",
)["Credentials"]

# Now exercise your code against scoped creds, not admin
s3_readonly = boto3.client(
    "s3",
    aws_access_key_id=creds["AccessKeyId"],
    aws_secret_access_key=creds["SecretAccessKey"],
    aws_session_token=creds["SessionToken"],
    endpoint_url="http://localhost:4566",
)

I caught two over-privileged policies this way in the first week. One was mine, and I’d spent months blaming the infra team for it.

Write IAM-enforced integration tests against Floci before you touch production policy. Failures you catch locally are free; the ones you catch in prod are audit findings.

Storage modes: pick your lie

FLOCI_STORAGE_MODE has four values and the right one depends entirely on the test you’re running. The defaults try to be sane, but “sane” means different things in unit tests and manual dev.

ModeWhat it doesUse for
memoryEverything vanishes on restartUnit tests, CI
hybridStateful services (S3, Dynamo) persist, rest in memoryLocal dev — the default
persistentEverything persists to diskManual QA, long-running fixtures
walWrite-ahead log for maximum durabilityReproducing rare data corruption bugs

I default to hybrid for dev and memory for CI. The one time you want wal is when a flaky test only fails on the 400th run and you’re trying to capture the state at the exact moment it goes wrong.

The footgun: setting persistent globally in CI. Your next pipeline run inherits yesterday’s half-broken DynamoDB table and you chase a ghost for two hours before checking the storage flag.

Where Floci isn’t the answer

Floci is not a superset of LocalStack. If your stack leans on Glue, Athena, SageMaker, or the long tail of AWS services, you’re still on LocalStack Ultimate — and honestly, for that kind of workload, the $89/month probably earns itself back. No emulator ever reaches 100% parity with real AWS, but LocalStack has a decade of edge cases mapped that Floci simply hasn’t seen yet.

Other places I’d think twice:

  • You already pay for LocalStack. If the CI budget absorbs it and your tests pass, migrating for philosophy alone is busywork.
  • CloudFormation with heavy cross-service wiring. Floci’s CFN coverage is growing, but intrinsic functions and deeply nested stacks still hit gaps.
  • Teams depending on Cloud Pods, snapshots, or the LocalStack dashboard. Those are commercial LocalStack features; Floci doesn’t pretend to have them.

For everything in the “S3 + SQS + Dynamo + Lambda + RDS” middle — which is most of the AWS work I actually do — Floci is the honest default now.

One actionable takeaway: spin Floci up on a side branch this week. Point your existing endpoint_url at port 4566 on the same container image and watch how many of your tests don’t notice. The ones that do notice are telling you something interesting about your stack, and it’s cheaper to learn it now than during an auth-token outage at 9 AM on a Tuesday.

Happy emulating. And go look at a sunset sometime — the popcorn-shaped clouds really are worth the name.

Sources

Thread

0
⌘/Ctrl+Enter to sendType / for commands · Tab to @mention