やってみた 2026年11月3日

AWSエミュレーター floci を Docker なしで動かしてみた。S3→SQS の通知は届き、Lambda は終了コード0のまま失敗していた

XECIN JavaAWSエミュレーターインフラ

GitHubのトレンドを眺めていたら、8位に floci-io/floci が入っていました。説明は「Light, fluffy, and always free - The AWS Local Emulator alternative」。スターは28,178で、その日だけで219増えています。言語は Java、ライセンスは MIT です。

README には、LocalStack のコミュニティ版が2026年3月に終わり、認証トークンが必要になったという一文があり、floci はその「no-strings-attached alternative」だと書かれています。S3 や SQS を使うコードのテストを CI でどう回すかは、私にとってもずっと悩みどころでした。運用を考えると、AWS アカウントも認証トークンも要らずに本物に近い動きをしてくれるなら、それだけで十分に価値があると考えています。

ただ、今回はルールとして、検証は使い捨てのコンテナの中で行い、その中でさらに Docker は動かしません。README の Quick Start は floci start(別リポジトリの CLI)か docker compose up です。そこで「Docker なしでどこまで使えるのか」を確かめることにしました。

試した環境

  • 使い捨てのDockerコンテナ(eclipse-temurin:25-jdk、Ubuntu 26.04.1 LTS)。メモリ4GB・CPU2つに制限し、Docker のソケットは渡していません
  • Temurin 25.0.5+8 / Maven 3.10.0(リポジトリ同梱の mvnw が取得したもの)
  • floci: リリース 2.4.0 のタグ(9be236e、2026-10-31)
  • AWS CLI 2.39.3

入れ方の設計判断:Docker を使わない経路はどれか

README には「No Docker?」という一節があり、各リリースに Linux・macOS・Windows 向けの単体バイナリ floci-aws が付く、とあります。インストールの文書にも、releases/download/ から floci-aws-linux-amd64 を curl で取ってくる手順が載っています。最初はこれで行くつもりでした。

ところが、2.4.0 の URL は 404 でした。API で見ると、2.4.0 のリリースに付いているファイルは0件です。2.3.0〜2.1.0 も0件でした。

不思議に思って、公開されているワークフローの実行結果を見に行きました。2.4.0 のリリース処理(release.yml)は failure で終わっていて、ジョブごとに見るとこうなっていました。

ジョブ結果
Build native artifact(amd64 / arm64)success
Push native and compat Docker imagessuccess
Build standalone binary(windows-amd64)failure(Smoke-test the standalone binary の段)
Build standalone binary(darwin-arm64 / darwin-amd64)cancelled
Publish standalone binariesskipped

Docker イメージは公開まで進み、単体バイナリだけが Windows 版のスモークテストで止まって、Linux 版も含めて公開されなかった、という形です。バイナリを添付する仕組みは10月17日のコミット(#5844)で入ったもので、その2日前に出た 2.3.0 にはもともとありません。つまり 2.4.0 が最初の機会でした。Docker で使う人には影響が無いので、気づかれにくい種類の漏れだと思います。

残る経路は、インストール文書の「Build from Source」です。必要なのは Java 25 以上と Maven 3.9 以上で、手順は2行でした。

最初のつまずき:README どおりのビルドが落ちる

# README の手順: ./mvnw clean package -DskipTests
[INFO] Compiling 2996 source files with javac [debug release 25] to target/classes
[INFO] Compiling 3297 source files with javac [debug release 25] to target/test-classes
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.16.0:testCompile (default-testCompile) on project floci: Compilation failure
[ERROR] unknown enum constant org.immutables.value.Value.Style.ImplementationVisibility.PACKAGE
[ERROR]   reason: class file for org.immutables.value.Value$Style$ImplementationVisibility not found
# build rc=1 elapsed=148646 ms

# テストのコンパイルごと飛ばす: ./mvnw clean package -Dmaven.test.skip=true
[INFO] Building jar: /work/floci/target/floci-2.4.0.jar
[INFO] BUILD SUCCESS
# build #2 rc=0 elapsed=38256 ms
96M	target/quarkus-app
340M	/root/.m2

-DskipTests はテストを「実行しない」だけで、テストのコードはコンパイルします。落ちたのはそのテストのコンパイル(3,297ファイル)でした。本体の2,996ファイルは通っています。上流の CI も ./mvnw test-compile を回しているので、どこでも落ちるわけではないはずです。今回の JDK 25.0.5 なのか、依存の取り合わせなのか、原因までは確かめていません。

回避は -Dmaven.test.skip=true で、テストのコンパイルごと飛ばします。これで38秒で通り、target/quarkus-app にjarが229個並びました。

正直なところ、これは「動かして使うだけ」なら十分ですが、自分で floci に手を入れてテストを回したい人には使えない逃げ道です。ここは使い方次第だと考えています。

起動:2.2秒、そして1行の WARN

java -jar target/quarkus-app/quarkus-run.jar で起動すると、ヘルスチェックが200を返すまで2,487ミリ秒でした。待ち受けは 127.0.0.1:4566 です。

WARN  [io.git.hec.flo.lif.DockerAvailabilityReport] Docker is not reachable at unix:///var/run/docker.sock (java.net.SocketException: No such file or directory). Without it these services keep their control plane but run no containers: [rds, elasticache, memorydb, opensearch, neptune, docdb, msk, eks, timestreaminfluxdb, ec2]; and these fail when asked to run one: [lambda, ecs, codebuild, batch, sagemaker, appsync, pipes (Kafka sources), mwaa, amazonmq, kinesisanalytics, redshift, redshift-serverless, rds-data, athena, ecr, codeartifact, verifiedpermissions]. Everything else works without Docker.
INFO  [io.quarkus] floci 2.4.0 on JVM (powered by Quarkus 3.40.1) started in 2.164s. Listening on: http://127.0.0.1:4566

# /_floci/health を jq で数えたもの
{"n":129,"byStatus":{"running":129},"lambda":"running","ecs":"running"}

Docker が無いことは、起動時の WARN 1行に全部書いてあります。コンテナを動かさない10サービスと、頼まれたら失敗する17サービスです。親切な書き方だと思います。

一方で、ヘルスチェックの /_floci/health は129サービスすべてを running と返しました。WARN で「失敗する」と名指しされた lambda も ecs も running です。CI で「ヘルスチェックが通ったら次へ進む」と書くのはよくある形なので、Docker が要るサービスを使うテストでは、このヘルスチェックだけでは準備ができたとは言えない、という点は覚えておく必要があります。

メモリは、起動直後の RSS で307MBでした。インストール文書には、Docker イメージ(中身はネイティブ版)について起動約24ms・メモリ約13MiBという数字がありますが、今回の JVM 版とは比べられません。私が測れたのは JVM 版の方だけです。

README の Quick Start と、自分の使い方

README の3つのコマンド(aws s3 mb / aws dynamodb create-table / aws dynamodb list-tables)は、create-table の出力を --query で短くした以外は書かれたとおりに打って、すべて終了コード0でした。環境変数は文書どおり AWS_ENDPOINT_URL=http://localhost:4566 で、キーは test / test です(floci env は CLI 側の機能なので使っていません)。

本当に知りたかったのは、私がよく組む「S3 に置いたら SQS に通知が来て、処理済みの印を DynamoDB に付ける」流れが通るかどうかです。

$ aws s3api put-bucket-notification-configuration --bucket inbox \
  --notification-configuration '{"QueueConfigurations":[{"QueueArn":"arn:aws:sqs:us-east-1:000000000000:uploads","Events":["s3:ObjectCreated:*"]}]}'
$ aws s3 cp /tmp/hello.txt s3://inbox/hello.txt
$ aws sqs receive-message --queue-url http://localhost:4566/000000000000/uploads --wait-time-seconds 10 --max-number-of-messages 10
# receive-message took 406 ms, messages=2
# 1件目の Body
{"Service":"Amazon S3","Event":"s3:TestEvent","Time":"2026-11-03T12:11:31.630278407Z","Bucket":"inbox","RequestId":"e027eb9c-6097-4970-9947-548920f2e5b9","HostId":"e0705b7f-9846-4f3b-8733-936d2d96159c"}
# 2件目の Body から jq で項目を抜き出したもの
{"eventSource":"aws:s3","eventName":"ObjectCreated:Put","awsRegion":"us-east-1","bucket":"inbox","key":"hello.txt","size":12,"eTag":"5d41071e084f4ff2d7a809e02a1c2fe5"}
# md5 of the file: 5d41071e084f4ff2d7a809e02a1c2fe5

$ aws dynamodb put-item --table-name demo-table --item '{"pk":{"S":"hello.txt"},"status":{"S":"again"}}' --condition-expression 'attribute_not_exists(pk)'
aws: [ERROR]: An error occurred (ConditionalCheckFailedException) when calling the PutItem operation: The conditional request failed
# rc=254

通知は2件来ました。1件目は s3:TestEvent で、時刻は通知を設定した 12:11:31。ファイルを置いた 12:11:32 より前のものです。この1件には Records がありません。受け側のコードが Records を前提にしていると、ここで落ちます。本番向けに書いた受け側をそのまま当てて、手元で一度つまずけるのは、むしろありがたいと考えています。

2件目が本物の ObjectCreated:Put で、eTag はファイルの MD5 と一致しました。DynamoDB の条件付き書き込みも、2回目は ConditionalCheckFailedException で弾かれ、項目は1回目の status=done のまま残っています。

この3点が通るなら、私の用途のテストはかなりの部分を floci で回せそうです。

Docker が無いときの Lambda は「終了コード0」で失敗する

WARN で「失敗する」と書かれていた Lambda も試しました。ok を返すだけの小さな Python 関数を zip にして作り、そのまま呼んでいます。

$ aws lambda create-function --function-name hello --runtime python3.12 --handler app.handler --role arn:aws:iam::000000000000:role/lambda-role --zip-file fileb:///tmp/fn/fn.zip --query '[FunctionName,State,StateReasonCode]' --output text
hello	Active	None
# rc=0
$ aws lambda invoke --function-name hello /tmp/out.json
{
    "StatusCode": 200,
    "FunctionError": "Unhandled",
    "ExecutedVersion": "$LATEST"
}
# rc=0
# invoke took 417 ms
$ cat /tmp/out.json
{"errorMessage":"Failed to start Lambda container: Docker is not reachable (No such file or directory). Lambda functions run in Docker containers.","errorType":"Lambda.InitError"}

関数の作成は Active で通り、invoke もコマンドとしては終了コード0、StatusCode は200です。失敗は FunctionError と出力ファイルの中身にだけ出ています。CI のスクリプトで aws lambda invoke の終了コードだけを見ていると、この失敗は素通りします。Lambda を呼ぶテストを floci に載せるなら、FunctionError を必ず見る書き方にしておくことが重要です。

もう一つ気づいたのは、関数を作った時点で awslambda-us-east-1-tasks というバケットが増えたことです(ログにも Created bucket: awslambda-us-east-1-tasks と出ます)。自分では作っていないので、「バケットの一覧が空であること」を確かめるようなテストを書いていると、ここで数が合わなくなったりします。

既定の「ゆるさ」と、それを締める設定

最後に、認証と再起動の扱いを確かめました。

# 既定の設定のまま、秘密鍵をわざと間違える
$ env AWS_SECRET_ACCESS_KEY=definitely-wrong aws s3 ls
2026-11-03 12:11:31 inbox
2026-11-03 12:11:21 my-bucket
2026-11-03 12:11:34 awslambda-us-east-1-tasks
# rc=0

# FLOCI_AUTH_VALIDATE_SIGNATURES=true で起動し直したあと
$ env AWS_SECRET_ACCESS_KEY=definitely-wrong aws s3 ls
aws: [ERROR]: An error occurred (SignatureDoesNotMatch) when calling the ListBuckets operation: The request signature we calculated does not match the signature you provided. Check your key and signing method.
# rc=254
$ env AWS_ACCESS_KEY_ID=AKIAEXAMPLEUNKNOWN AWS_SECRET_ACCESS_KEY=x aws s3 ls
aws: [ERROR]: An error occurred (InvalidAccessKeyId) when calling the ListBuckets operation: The AWS Access Key Id you provided does not exist in our records.
# rc=254

既定では、秘密鍵は何でも通ります。文書にも「the secret access key can be any non-empty string」とあるので仕様どおりです。FLOCI_AUTH_VALIDATE_SIGNATURES=true を付けて起動し直すと、test / test はそのまま通り、間違った鍵だけが弾かれました。SQS に同じことをすると InvalidSignatureException で、サービスごとにそのサービスらしいエラー名で返ってきます。

再起動の扱いも含めて、表にしておきます。

確かめたこと既定設定を足したとき
秘密鍵を間違えて s3 ls通る(rc=0)FLOCI_AUTH_VALIDATE_SIGNATURES=true で SignatureDoesNotMatch(rc=254)
知らないアクセスキーで s3 ls通る(rc=0)同上で InvalidAccessKeyId(rc=254)
再起動したあとの中身全部消える(s3 ls は空、テーブルは0個)FLOCI_STORAGE_MODE=persistent で、バケット・オブジェクト・項目が残る(保存先に205ファイル・844KB)
ヘルスチェックが200になるまで2,357ms(2回目の起動)署名検証ありで2,308ms、永続化ありで2,479ms・2,650ms

永続化の保存先には、今回触っていない EC2 や RDS の分も含めて205個のファイルができていました(名前が ec2 で始まるものが33個、rds が20個)。合計は844KBです。どちらの設定を足しても、起動の時間はほとんど変わりませんでした。

試してみての所感

お金の面では、AWS アカウントも認証トークンも要らないので0円です。代わりに手元のマシンのコストを並べると、今回の JVM 版はビルドに約3分(1回目の失敗148.6秒と2回目の38.3秒)、Maven のキャッシュが340MB、成果物が96MB、起動後の RSS が307〜341MBでした。README の比較表にある約40MBの単体バイナリが届けば、このうちビルドの分はまるごと要らなくなります。今回はそれが手に入らなかった、というのが正直な結果です。

私は、S3・SQS・DynamoDB のように Docker が要らないサービスのテストから floci に載せていくつもりです。その際は FLOCI_AUTH_VALIDATE_SIGNATURES=true を付けて、鍵の取り違えも手元で落ちるようにします。Lambda を呼ぶテストは、FunctionError を見る形に直してからにします。

Docker が要るサービスと単体バイナリは、次のリリースで添付されるのを待って、改めて試したいと考えています。

今思えば、README の「No Docker?」を読んだ時点で、リリースの添付ファイルを一度見に行っておけば、もう少し早く経路を決められたかもしれません。