Skip to main content

fakecloud and Floci: two free LocalStack alternatives, side by side

Kostiantyn Lysenko
Author
Kostiantyn Lysenko

I needed a local AWS environment — a sandbox to poke at services without the pain of a properly configured account. For a few weeks that was Floci, an open-source drop-in replacement for LocalStack Community. The best free option I found — I wrote about it.

Then I wanted CloudFront in my local emulator. Floci doesn’t implement it. LocalStack ships CloudFront only in its paid tiers.

fakecloud (also free and open source) does implement CloudFront. And Route 53. And WAFv2. And Cognito. And Bedrock. 105 services, a single ~19 MB binary, no auth tokens, no account. Btw, it’s written in Rust :)

So I set up both, side by side: one repo, two Docker Compose profiles, one justfile. Switch engines per workload.

What we are building
#

~/git-repos/local-aws/
├── docker-compose.yml   # both services, gated by profiles
├── justfile             # task runner, engine-parameterized
└── data/
    ├── floci/           # Floci persistent state
    └── fakecloud/       # fakecloud persistent state

Both engines bind localhost:4566, both accept test/test credentials. Only one runs at a time (port collision). Swap is just down && just up <engine>.

docker-compose.yml
#

The Floci service lives in the same file behind its own profile. Here is the fakecloud half:

services:
  fakecloud:
    image: ghcr.io/faiscadev/fakecloud:latest
    container_name: fakecloud
    profiles: ["fakecloud"]
    restart: unless-stopped
    ports:
      - "4566:4566"
    environment:
      FAKECLOUD_STORAGE_MODE: persistent
      FAKECLOUD_DATA_PATH: /var/lib/fakecloud
      FAKECLOUD_REGION: us-east-1
      FAKECLOUD_ACCOUNT_ID: "000000000000"
      FAKECLOUD_LOG: info
      FAKECLOUD_ADDR: "0.0.0.0:4566"
    volumes:
      - ./data/fakecloud:/var/lib/fakecloud
      - /var/run/docker.sock:/var/run/docker.sock
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:4566/_fakecloud/health"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 10s

Three things to notice:

  1. Profiles. Each service sits behind a Compose profile of the same name. docker compose up with no profile starts nothing. docker compose --profile fakecloud up -d starts only fakecloud. That keeps the engines mutually exclusive — no conditional includes.
  2. The Docker socket is not decoration. fakecloud runs Lambda in real Docker containers with the official AWS runtime images. RunInstances boots a container per EC2 instance — user-data and all. Without the socket, EC2 degrades to metadata-only instances.
  3. Health paths differ. Floci exposes /_localstack/health (a deliberate LocalStack-compat alias). fakecloud uses /_fakecloud/health and returns its own JSON shape: {"status":"ok","version":"0.41.1","services":[...]}.

justfile
#

set shell := ["bash", "-cu"]

engine := env_var_or_default("AWS_EMU", "floci")

default:
    @just --list

up engine=engine:
    docker compose --profile {{engine}} pull
    docker compose --profile {{engine}} up -d
    @echo "{{engine}} at http://localhost:4566"

down:
    docker compose --profile floci --profile fakecloud down

restart engine=engine: down (up engine)

logs engine=engine:
    docker compose logs -f {{engine}}

health engine=engine:
    @if [ "{{engine}}" = "floci" ]; then \
        curl -fsS http://localhost:4566/_localstack/health | { command -v jq >/dev/null && jq . || cat; }; \
    else \
        curl -fsS http://localhost:4566/_fakecloud/health | { command -v jq >/dev/null && jq . || cat; }; \
    fi

wipe engine=engine:
    docker compose --profile {{engine}} down
    rm -rf data/{{engine}}
    @echo "{{engine}} state wiped."

AWS_EMU=fakecloud sticks the engine for the shell. Without it, just up defaults to Floci.

AWS CLI env
#

~/.bash.d/aws.sh is sourced on every shell start:

# Local AWS emulator (Floci or fakecloud) at http://localhost:4566
# Switch engine: AWS_EMU=fakecloud just up   (default: floci)
# Override per-shell when targeting real AWS (e.g. unset AWS_ENDPOINT_URL).
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

Same env shape works for both engines. Real AWS is one unset AWS_ENDPOINT_URL away.

Swap and smoke test
#

With Floci running:

just down
just up fakecloud
just health fakecloud

just health fakecloud after a few seconds:

{
  "status": "ok",
  "version": "0.41.1",
  "services": [
    "s3", "cloudfront", "wafv2", "route53", "cognito-idp", "bedrock",
    "lambda", "dynamodb", "sqs", "sns", "kms", "iam", "sts", ...
  ]
}

105 services in the full list — CloudFront and WAFv2 included. Quick end-to-end:

aws s3 mb s3://tubely-private-13656
aws s3 cp ./video.mp4 s3://tubely-private-13656/
aws cloudfront list-distributions

All return 200. To create a distribution with a WAFv2 ACL in monitor (count) mode:

aws wafv2 create-web-acl --region us-east-1 \
  --name tubelycdn-monitor \
  --scope CLOUDFRONT \
  --default-action Allow={} \
  --rules '[{"Name":"AWS-AWSManagedRulesCommonRuleSet","Priority":0,
            "Statement":{"ManagedRuleGroupStatement":{"VendorName":"AWS","Name":"AWSManagedRulesCommonRuleSet"}},
            "OverrideAction":{"Count":{}},
            "VisibilityConfig":{"SampledRequestsEnabled":true,"CloudWatchMetricsEnabled":true,"MetricName":"common"}}]' \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=tubelycdn

aws cloudfront create-distribution --distribution-config file://dist-tubelycdn.json

You get back a distribution ID, an ARN, and a *.cloudfront.net domain.

What fakecloud is good at
#

APIs are clean and Smithy-perfect. Where behavior is simplified, the docs say so upfront: CloudFront’s data plane is a single local node; it forwards requests but doesn’t cache them. For my purposes that never mattered. Lambda and EC2 run in real containers — real execution.

fakecloud is also open to contributions, and reviews move fast. EC2 output was missing PlatformDetails, so I sent a PR. Reviewed and merged the same day. Very active and friendly maintainers.

When to reach for each
#

NeedEngine
Tooling or CI that expects LocalStack conventions (/_localstack endpoints, drop-in swap)Floci
CI fixture: deterministic API responses, plan/apply assertions, fast startupfakecloud
CloudFront, Route 53, WAFv2, Cognito, Bedrock — services Floci skipsfakecloud
Docker-backed compute: Lambda, EC2, RDS, ElastiCache, ECS as real containerseither — both do it
Comparing two engines against the same codeboth, swap via profile

Wrap-up
#

I started with Floci, and it earned its slot: drop-in LocalStack replacement, no license, no fuss. But fakecloud at 0.41 became my default engine. More services, real containers behind Lambda, EC2, RDS, and ElastiCache, plus CloudFront, Route 53, and WAFv2 that Floci skips. Floci keeps its Compose profile for tooling that expects LocalStack conventions. Two open-source emulators, one Docker Compose file, one localhost:4566 endpoint, over a hundred services between them. No paid licenses. No internet required.

References
#


comments powered by Disqus