こんにちは。エンジニアのjunです。ゴールデンウィークはプラレール博にいきました。会場限定の車両が欲しかったので息子に相談しましたが、要らないと一蹴されて断念し、彼が欲しがったミスタードーナツの移動販売のトミカを買いました。
さて今回は、保育園探しWebサービスであるホイシルの開発環境の改善のために、Floci(※ フローシーと読むのが正しいようです)というローカルAWSエミュレータを開発環境に導入したお話をさせていただきます。
どのような背景から必要になり、なぜFlociを選択し、何が得られたのか、Flociの特徴などについて説明していきます。
- ホイシルにおけるAWS利用とローカル開発での課題
- これまでの試行錯誤と再チャレンジまでの時系列
- 候補比較:必要なCognito APIから逆引きで評価する
- Flociとは
- Flociのアーキテクチャと、AWSの仕様変化への追従
- ホイシルでの使い方
- 不足機能とPull Request
- 導入で得られたもの
- リスクと対策
- さいごに
ホイシルにおけるAWS利用とローカル開発での課題
ホイシルでは、システム上でいくつかのAWSのマネージドサービスを利用しています。保護者のユーザーから見える機能で代表的なものは次の2つです。
- Cognito: ユーザーの認証(IdP)
- S3: 園の写真など、画像ファイルのオリジン
簡略化するとこのような典型的な構成になっています。
flowchart TB
subgraph clients[" "]
direction LR
parent["保護者向けWebサイト<br/>(Nuxt 3)"]
api["Laravel<br/>APIサーバ"]
facility["施設向けサイト<br/>(Nuxt 3)"]
end
subgraph aws["AWS"]
direction LR
s3["S3"]
cognito["Cognito"]
end
parent --> api
facility --> api
api --> s3
api --> cognito
これらのサービスを手元のPCで開発する際、開発者は共有の開発用AWS環境に接続して開発を行っていました。Cognito User Pool も S3 バケットもチームで共有するリソースを使っていた、ということになります。
この運用自体は以前から続けてきたものですが、最近になって課題として浮き彫りになってきました。AIによるコーディングで開発速度が上がり、実装サイクルが短くなりコードの出力量も増えてきたことで、次のような需要が強くなってきたためです。
- e2eテストを高頻度で実行して早期にフィードバックを得たい
- データに依存したテストケースを増やして品質を担保したい
- CIでe2eテストを回してマージ前に問題を検知したい
これらの需要は構造的にはAI以前からあったものですが、開発速度が上がることで課題感が強くなってきていました。
ところが本物のAWSにつなぐ運用では、次に示す理由からこれらの需要を満たしにくい状況でした。
① 開発者間でのリソース干渉・データ汚染
共有の開発用Cognito User Pool に同じメールアドレスでユーザーを作ろうとして衝突したり、S3のテストオブジェクトが他の開発者の作業を巻き込んだりといった事故が起こります。ないしは、並行に別のテストシナリオを動かそうとすると、それぞれが同一の状態を参照してしまい失敗してしまうという問題がつきまといます。
② CI耐性のなさ
GitHub Actions から本物のAWSに接続するには、OIDC連携や専用のIAMロール整備など準備すべきものが多くあります。安直にCI用のIAMユーザーを作って静的なアクセスキーを持たせるのは現代のセキュリティ運用としてはアンチパターンですし、仮にやったとしても、CIワーカーから共有のCognito User Pool を並行に叩く構造はそもそも上で挙げた干渉問題と同じ壁にぶつかります。
こうした事情から、ホイシルではe2eテスト(Playwright & Gauge)を開発者がローカルで実行してマージ前に確認するという運用にしていました。とはいえ人間が手で回す以上、実行漏れも発生しがちで、フィードバックループとしては理想的ではない状態が続いていました。
こういった問題に対しては、LocalStack のようなローカル向けのAWSエミュレータを開発環境に組み込むのが、Web開発のプラクティスとしては定石だと思います。この方向性は当然検討してきたのですが、無償で要件を満たせる手段がなかなか見つからなかったというのが正直なところです。次の章でその試行錯誤の経緯をお話ししていきます。
これまでの試行錯誤と再チャレンジまでの時系列
ホイシルにおける「ローカルAWSエミュレータ」の検討は、実は今回が初めてではありません。
これまでにも2回ほど手を動かしているのですが、順に振り返るといずれもCognitoのところで詰まっていました。
LocalStackの検討
2025年の夏頃、最初に検討したのが王道のLocalStackでした。ところがLocalStackではCognitoはずっと以前からPro(有償)プランの対象機能として位置づけられており、Community版では使えませんでした。ホイシルにおいてCognitoは認証の中核であり、ローカル開発でこれが動かないと先に進めません。この時点では他の選択肢も特に見当たらず、「LocalStackで一気に解決」というシナリオは諦めることになりました。
motoのサーバモード
次に試したのが moto のサーバモードです。motoはPython製のAWSモックライブラリで、もともとはユニットテスト用ですが、moto_server を使うことでスタンドアロンのモックサーバとしても動かせます。
ホイシルでは検討の手応えを掴むために、Docker Composeに組み込んで実際にアプリケーションから繋いでみるレベルまで評価を進めました。普通に起動して普通に叩ける程度には動きます。
ただし、Custom Auth Flow が期待通りに動かないという壁にぶつかりました。ホイシルではCognitoの Custom Auth Flow(DefineAuthChallenge / CreateAuthChallenge / VerifyAuthChallengeResponse という Lambda Trigger を介したカスタム認証)を利用しているため、ここが動かないとローカル環境としては実用になりません。
motoのソースコードを覗いてみると、initiate_auth の実装 では CUSTOM_AUTH が有効な認証フローとして受理されるものの、チャレンジ生成・検証・トークン発行といったフローを進めるための処理分岐が用意されていないことが確認できます1。コードを読む限り、CUSTOM_AUTH を投げると分岐の最後の else まで素通りして実質的に何も返さない、という挙動になっているように見えます。
motoはあくまで「AWSのAPIを叩いた時のレスポンス」をモックするライブラリであり、Cognito の Custom Auth Flow のような状態遷移までは再現対象に含めていないようです。IMPLEMENTATION_COVERAGE.md を見ても、cognito-idpのカバレッジは50%程度に留まっています。これはmotoというツールの設計思想として妥当な割り切りだとは思うのですが、ホイシルのユースケースとは噛み合わず、moto案も見送りとなりました。
ライセンス周りの状況変化を受けて、純粋なOSSを軸に再チャレンジ
状況が変わったのは、2025〜2026年にかけてのライセンス周りの動きでした。LocalStackからは有償化アナウンスが2026年1月に出され、続いて2026年3月にはCommunity版終了通知(Important Updates to Pricing & Packaging)が出ました。
この一連のアナウンスを受けて、Kumo や Floci など MITライセンスの新興OSSローカルAWSエミュレータがいくつか登場してきていたこともあり、「もう一度ローカルAWSエミュレータに挑む価値がある」と判断し、再検討をすることとなりました。
再チャレンジにあたっての要件
過去の試行錯誤から、今回は最低限満たしておきたい要件を以下のように整理しました。
- Cognitoの必要機能が動くこと。具体的には User Pool / Client の基本操作に加えて、Custom Auth Flow がアプリ視点で破綻なく動くこと
- 無償・OSSで、商用利用可能なライセンスであること
- 起動・実行が軽く、CIでも気軽に回せること
これらの要件を持って候補を見直したところ、いくつかのOSSが視野に入ってきました。
候補比較:必要なCognito APIから逆引きで評価する
ローカルAWSエミュレータを選ぶ際、各ソリューションが公開している「対応サービス一覧」を眺めて評価する方法が一般的だと思います。ただ今回は、これまでの試行錯誤の経緯からも分かるように「Cognitoの中でもどのAPIをどう実装しているか」がボトルネックになることが分かっていました。ベンダー側の粒度ではなく、ホイシル側の粒度で評価軸を作る必要がある、ということです。
そこで今回は以下のアプローチを取りました。
- ホイシルのコードベースから、Cognitoで実際に呼び出しているAPIを洗い出す
- その一覧をベースに、各候補がそれぞれのAPIをどう実装しているかを確認する
Step 1: ホイシルが叩いているCognito API一覧
Claude Codeを利用してバックエンドのコードベースを走査し、CognitoのSDKを通じて実際に呼び出している操作を列挙してもらいました。
その結果、ホイシルで利用しているCognito APIは以下の9操作に整理できました。
| # | API | 用途 |
|---|---|---|
| 1 | SignUp |
ユーザー登録 |
| 2 | ConfirmSignUp |
確認コードによるユーザー有効化 |
| 3 | AdminInitiateAuth |
サーバ起点での認証開始(Custom Auth Flow含む) |
| 4 | AdminRespondToAuthChallenge |
チャレンジへの応答 |
| 5 | ForgotPassword |
パスワード忘れフローの開始 |
| 6 | ConfirmForgotPassword |
新パスワードによるリセット完了 |
| 7 | ChangePassword |
ログイン中のパスワード変更 |
| 8 | AdminDeleteUser |
ユーザー削除(退会処理) |
| 9 | AdminGetUser |
ユーザー属性取得 |
加えて、ホイシルは認証に Custom Auth Flow を使っているため、 「認証開始 → チャレンジへの応答 → トークン発行」 という一連のフローが破綻なく動くこと が必須要件になります。
本番のCognitoでは DefineAuthChallenge / CreateAuthChallenge / VerifyAuthChallengeResponse という Lambda Trigger がこの状態遷移を駆動しますが、ローカルAWSエミュレータの場合は少なくともe2eシナリオを通す目的では、そのLambdaを実際に呼ばずともエミュレータ側で状態遷移を成立させてくれていれば十分という見方もできます。
Step 2: 各候補のCognito実装をAPI単位で評価する
この9操作を軸に、候補それぞれの実装状況を確認しました。表中の ✅ / × は APIエンドポイントが受理されるかどうかではなく、ホイシルのユースケースで実用に動くかどうか で判定しています。Cognitoの場合、特に Custom Auth Flow まわりではAPIは受理するけれどフローを進める内部実装を持っていないというケースが多いので、そこを切り分けて評価する必要がありました。
各候補の参照バージョンは以下に揃えています。
- LocalStack: v4.14.0(2026-02-26 リリース、Community版終了の直前)
- moto: v5.2.1(2026-05-10 リリース)
- Kumo: v0.9.0(2026-04-16 リリース、評価時点の最新)
- Floci: v1.5.10(2026-04-30 リリース、コミット
f30bd79d)
| API | LocalStack v4.14.0 (Community) | moto v5.2.1 | Kumo v0.9.0 | Floci v1.5.10 |
|---|---|---|---|---|
SignUp |
× (Pro機能) | ✅ | ✅ | ✅ |
ConfirmSignUp |
× (Pro機能) | ✅ | ✅ | ✅ |
AdminInitiateAuth |
× (Pro機能) | × (※1) | × | ✅ |
AdminRespondToAuthChallenge |
× (Pro機能) | × (※1) | × | × (※2) |
ForgotPassword |
× (Pro機能) | ✅ | × | ✅ |
ConfirmForgotPassword |
× (Pro機能) | ✅ | × | ✅ |
ChangePassword |
× (Pro機能) | ✅ | × | ✅ |
AdminDeleteUser |
× (Pro機能) | ✅ | ✅ | ✅ |
AdminGetUser |
× (Pro機能) | ✅ | ✅ | ✅ |
| Custom Auth Flow の状態遷移 | × | × | × | ✅ (※3) |
| 実装言語 / ランタイム | Python | Python | Go | Java / GraalVM (Native Image) |
| ライセンス | Apache 2.0(一部Pro有償) | Apache 2.0 | MIT | MIT |
(※1) motoは AdminInitiateAuth / AdminRespondToAuthChallenge のAPI自体は受理しますが、前章で見たとおり CUSTOM_AUTH フローを進める内部実装が用意されておらず、Custom Auth Flowを使うホイシルのユースケースでは実用にならないため、本表では×としています。
(※2) 評価時点の Floci v1.5.10 では RespondToAuthChallenge までは実装されていましたが、サーバ起点版の AdminRespondToAuthChallenge だけは未実装という状態でした。あとで触れますが、この1操作については追加実装してupstreamに送り、PR #666 として v1.5.11 に取り込んでもらうことで解消しています。
(※3) Floci自体は DefineAuthChallenge / CreateAuthChallenge / VerifyAuthChallengeResponse の Lambda Trigger を実際にFloci上のLambdaサービスへ呼び出す経路を持っていますが、Lambdaが配備されていないときは内部のプリセットにフォールバックします。CUSTOM_CHALLENGE を返し続け、チャレンジに正答した履歴があれば issueTokens、3回不正解で failAuthentication を返す、という挙動です。ホイシルではLambdaを上げない後者の経路で運用しており、e2eシナリオを通す目的としては十分です。詳細は CognitoAuthFlowHandler.java を参照してください。
Flociを選んだ理由と、他候補がフィットしなかったところ
Floci が4候補のなかでもっとも条件に合っていたので採用しました。決め手としては、9操作中8操作が実装済みで、唯一足りなかった AdminRespondToAuthChallenge も、コードベースを読んだ限り既存の RespondToAuthChallenge から派生させれば書ける範囲に見えたためです。必要なら自分たちで埋めるとしても、そのコスト感が現実的でした。
加えて、Custom Auth Flow の状態遷移を Floci 内部でステートマシンとして実装している唯一の候補だったので、Cognitoの認証フローを通すという最重要要件もクリアしています。Lambda Trigger を実 Lambda として呼ぶ経路もありつつ、Lambda が配備されていない場合はプリセットへのフォールバックでフローを完結させてくれるため、ホイシルのe2eテスト用途では十分に動きます。足りなかった1操作については「不足機能とPull Request」のところで詳しく触れます。
他の候補がホイシルにフィットしなかった理由もまとめておきます。
- LocalStack はCognito関連がそもそもPro機能の枠に入っており、Community版で叩いてもエラーが返ってきます。今回の前提(無償・OSS)と相容れないので候補から外れます。
- moto はAPI自体は受理されるものの Custom Auth Flow を進める内部実装が無く、ホイシルの認証シナリオが成立しません。
- Kumo は評価時点でCognitoのサポートが9操作中4操作にとどまっていました。特に
AdminInitiateAuth/AdminRespondToAuthChallengeが未対応で、認証の入口に当たる操作が抜けているため、ホイシルの認証フローを通せる状態ではありません。今回はFlociでもKumoでも、足りない機能があれば自分たちでPull Requestを出していくつもりではあったのですが、Kumoの場合は埋めるべき差分が大きかったので断念しました。今後の状況次第ではFlociを置き換える可能性もあると思います。
実装言語 / ランタイムの観点も付け加えておきます。LocalStack / moto がPython、KumoがGo、FlociがJavaという形でしたが、個人的にKotlinやScalaを含むJVM言語の経験があり、手を入れることを想定したときに一番読み書きしやすいのがJavaだったというのも、Flociを選ぶ理由のひとつになりました。加えてFlociは GraalVM Native Imageでビルドされているため、ローカル開発やCIで気軽に立てたり落としたりできる軽さもあります。このあたりは技術的にも興味深いです。
Flociとは
ここで、採用に至ったFlociについて少し深掘りしておきます。
Flociは floci-io/floci で開発されている、MITライセンスのローカルAWSエミュレータです。2026年2月に公開されたばかりの比較的新しいプロジェクトですが、執筆時点で7000以上のスターと34リリース、平均で週2〜3リリースのペースで非常にアクティブに開発されています。
Flociの特徴を一言でまとめると、Java / GraalVM Native Image で実装された、軽くて起動が速いLocalStack代替です。READMEの Why Floci? セクションに掲載されているLocalStack Community版との比較数値を、そのまま引用させてもらいます。
| Floci | LocalStack Community | |
|---|---|---|
| 起動時間 | 約24 ms | 約3.3 s |
| アイドル中メモリ使用量 | 約13 MiB | 約143 MiB |
| Dockerイメージサイズ | 約90 MB | 約1.0 GB |
| Native binary | あり(約40 MB) | なし |
なお、この数値はFloci側の公表値であり、自分で同条件のベンチマークを取り直したものではありません。ただ実際に手元で動かしても、起動が体感で速く、コンテナとして気軽に立て落としできることは確認できています。
カバーしているAWSサービスはCognito以外にも幅広く、API Gateway v2、ElastiCache、RDS、MSK、Athena(DuckDB sidecar)、Glue、Data Firehose、S3、DynamoDB、IAM、STS、Kinesis、KMS、ECS、EKS、EC2、CodeBuild、CodeDeploy、Auto Scaling、SSM、Transfer Familyなど、主要なAWSサービスが幅広く揃っています。
Flociのアーキテクチャと、AWSの仕様変化への追従
ローカル開発のような長く使うことを前提にした技術選定では、「いま動く」だけではなく「これからもメンテされていきそうか」にも一定の見立てを持っておきたいところです。LocalStackが有償化に踏み切った背景にも、AWSという巨大に変化し続けるサービス群を無償OSSで追いかけ続けることの難しさがあると思われ、それはFlociでも構造的には変わらないはずです。
この章では、Flociの内部アーキテクチャを概観しつつ、その手の構造的な難しさにFlociがどう向き合えそうかを、AWS SDKsチームからFlociに届いた1本のissueを材料に考えてみます。あくまで現時点の観測と期待であって、将来を約束するものではない、というのは先に断っておきます。
Flociのアーキテクチャ概観
Flociはコードを覗いてみると、GraalVM Native Image でのビルドに対応したJavaフレームワークの Quarkus を主体に組まれています。GraalVM Native Image でAOTコンパイルすることで、Java製でありながら起動24ms・メモリ13MiBという軽量さを実現しているのがFlociの売りで、これはそのままQuarkusの特徴でもあります。HTTP受け口・DI・設定といったWebアプリの基盤層はQuarkusが担い、独自ポートのリスナーや各種ポーラーなど一部の低レイヤー機能で内部的に Vert.x のAPIも直接使われている、という構造です。
そのQuarkusの上に、AWSのワイヤープロトコルを処理する共通層が載っていて、そこから各AWSサービスの実装に振り分ける、というのが大まかな全体像になります。
flowchart TB
subgraph clients[" "]
direction TB
sdk["AWS SDK / CLI"]
db["MySQLクライアント等"]
end
subgraph floci["Floci プロセス"]
direction TB
web["HTTP 受け口 (Quarkus)"]
proto["AWS プロトコル層<br/>(QUERY / JSON / CBOR / REST_JSON / REST_XML)"]
subgraph ctrl["各 AWS サービス実装"]
direction LR
cognito["Cognito-IDP<br/>(内部ロジック)"]
athena["Athena<br/>(DuckDB プロキシ)"]
rds_ctrl["RDS 制御プレーン<br/>(CreateDBInstance 等)"]
other["…他多数"]
end
subgraph wire["データ用ワイヤープロトコル層 (Vert.x TCP)"]
direction LR
mysql_h["MySQL / Postgres ハンドラ"]
sigv4["SigV4 検証<br/>(IAM auth)"]
redis_h["Redis / Valkey ハンドラ"]
kafka_h["Kafka ハンドラ"]
mysql_h --> sigv4
end
end
subgraph backends["実物コンテナ"]
direction LR
mysql_be[("mysql:8.0")]
valkey_be[("valkey")]
kafka_be[("redpanda")]
end
sdk -->|HTTPリクエスト| web
web --> proto
proto --> cognito
proto --> athena
proto --> rds_ctrl
proto --> other
rds_ctrl -.プロキシ起動.-> mysql_h
rds_ctrl -.コンテナ起動.-> mysql_be
db -->|MySQL ワイヤープロトコル| mysql_h
sigv4 -->|検証OK後<br/>透過転送| mysql_be
redis_h --> valkey_be
kafka_h --> kafka_be
中央の「AWS プロトコル層」は、AWS SDK が送ってきたリクエストを どのワイヤープロトコルで解釈してレスポンスを返すか を決めて、共通的なエンコード / デコードを担当している層になります。ServiceProtocol という enum で、現時点で次の5種類を扱えるようになっています(ServiceProtocol.java より)。
QUERY— 旧来のフォームエンコード形式JSON— JSON 1.0 / 1.1(Cognitoなどが利用)CBOR— RPC v2 CBOR(バイナリ)REST_JSON— S3 をはじめとした REST 系REST_XML— S3 の一部レスポンスなど
そしてサービスごとに、「Floci内部にロジックを持って完結させる」ものと「実物のソフトウェアにプロキシして本物っぽく振る舞わせる」ものに分かれて実装されています。たとえばCognitoやIAMなどは内部ロジック、AthenaはDuckDB、RDSはPostgreSQL/MySQL、ElastiCacheはValkeyにプロキシすることで、実際のAWS上で動くサービスにできるだけ近づける、という構造です。後者の「実物にプロキシする」場合は、対応するソフトウェアをコンテナとして起動した上でプロキシする形になります。具体的なサービスと利用コンテナイメージの対照表は、READMEの Real Docker Integration セクションを参照してください。
なお、Flociプロセスが受けるリクエストは Quarkus 経由のHTTPだけではありません。RDSやElastiCache、MSKのように「実物のソフトウェアにプロキシする」タイプのサービスでは、AWS SDK が叩くHTTPの制御プレーンとは別に、MySQL / PostgreSQL / Redis / Kafka といったデータ用のワイヤープロトコルを直接受けるTCPリスナーを Floci プロセス内に持っています。この部分は Quarkus ではなく内部的に Vert.x のAPIでソケットを開いていて、AWSのHTTP APIとは別ポートで待ち受けています。
たとえばRDS(MySQL)では、CreateDBInstance をFlociが受けたタイミングでバックエンドの mysql:8.0 コンテナを起動し、それと一対一に対応する MySQLプロトコル用のTCPプロキシを Floci プロセス内に立ち上げます2。DescribeDBInstances のレスポンスで返す Endpoint は MySQLコンテナを直に指すのではなく、このプロキシのアドレス(既定では localhost:<割当ポート>)が入ります。クライアントが接続してくると、プロキシは認証ハンドシェイクだけインターセプトしてIAM authトークンを SigV4 で検証し、それ以降は実MySQLにバイト列を透過的に流す、という作りになっています。本物のAWSが受け持っている認証部分だけ Floci 側で再現し、SQL実行そのものは実エンジンに委ねる、というのが「実物にプロキシ」の中身です。
「AWS APIを内部ロジックで再現する」ことの難しさ
エミュレータが内部ロジックで自前にAWS APIを再現する際の本質的な課題は、AWSの各サービスが採用しているワイヤープロトコルの多様化と、その追従コストにあります。AWSは内部的に Smithy という独自IDL(Interface Definition Language)でサービス仕様を記述しており、サービスごとに異なるプロトコル(JSON 1.1、Query、REST、近年では RPC v2 CBOR など)を選んで採用しています。
AWSが既存サービスに新プロトコルを追加することもあり、たとえば 2025年12月のCloudWatchによる JSON/CBOR プロトコル対応の追加 のように、いつ・どのサービスで・どのプロトコルが追加されるかは、外部のエミュレータからは把握しにくい構造になっています。
エミュレータがOSSとしてこれを追従し保守していくのは、サービス数とプロトコル数が増えるほど確実に重荷になっていきます。
仕様追従に対するFlociのアプローチと、私たちの見立て
その追従コストに対して、AWS と Floci の両方から興味深い動きが出ています。AWS SDKs チームから Floci のリポジトリに直接立てられた floci-io/floci#156: Important AWS Update - Service wire protocol selection というAWS SDKチームのkstich 氏が起票したissueがあります。
要旨としては、2026年4月以降にAWSが追加のワイヤープロトコル(CBORなど)を順次採用していく予定で、事前通知は限定的にしかできない。なのでFlociのような「AWSサービスを名乗るサーバ」側で、Smithy 2.0 の Wire Protocol Selection Guide の "Identification for claiming" に沿って、クライアントが送ってきたリクエストからプロトコルを reactive に識別する実装に寄せてほしい、という依頼です。これに対して Floci 側も 前向きなコメント で応じています。
これが何を意味するかというと、 これまでエミュレータがリバースエンジニアリングで後追いしていたワイヤープロトコル識別の部分が、公開ガイドに沿った実装に寄せられる兆しがある ということです。AWS SDK チームから関心が向けられていることこと、そしてFloci メンテナの側にも継続的に維持していく姿勢が見えること、これらはエコシステムとしてポジティブな材料です。Flociのコード側もすでに ServiceProtocol enum に CBOR を含むなど新しいプロトコルに足し込みやすい構造を持っていて、AWSの実物に挙動を寄せる工夫が継続されている点も評価できます。
ただし、Smithyガイドの守備範囲はあくまでワイヤープロトコル識別の話で、サービス固有のビジネスロジックは当然ながらFlociの自前実装になります。AWS が今後も Smithy モデルを継続的に公開・更新してくれるかも、明文化された約束があるわけではありません。
これはすべてのOSSプロジェクトに対して言えることですが、それらの持続性は保証されたわけではなく、 自分たちが使う部分については最悪自前で面倒を見るくらいの覚悟は必要 だと思っています。後ろの章で触れる PR #666 のような形でupstreamに還元していくのも、それを実践しているものです。
issue #156 の続きや、その先で smithy-java(Smithy公式のJava実装)にどこまで乗っていくのか、といったあたりは Slackや GitHub Discussions でウォッチしていこうと思っています。
ホイシルでの使い方
ここからは、ホイシルの開発環境で実際にFlociをどう組み込んでいるかを紹介していきます。
ホイシルではローカル開発環境を Docker Compose で一括管理しています。バックエンド(Laravel)、フロントエンド(Nuxt 3)、データベース(MySQL)、リバースプロキシとSSL証明書発行(Caddy)、メール送信のモック(SendGrid Mock)、外部API用の WireMock などを1つの compose.yml に並べて、 make setup 一発で立ち上げられるようにしてあります。今回のFloci導入も、この compose.yml に「もう1つコンテナを足す」というかたちで素直に乗せられました。
全体の流れをシーケンス図にすると、このようになります。
sequenceDiagram
participant Dev as 開発者
participant Compose as Docker Compose
participant Floci
participant Setup as setup-floci.sh
participant Env as backend/.env
participant App as Laravel (CognitoClient)
Dev->>Compose: make setup / make start-all
Compose->>Floci: floci コンテナ起動 (port 14566)
Dev->>Setup: make setup-floci
Setup->>Floci: aws --endpoint-url=... cognito-idp create-user-pool / create-user-pool-client
Setup->>Env: UserPoolId / ClientId / ClientSecret を書き戻す
App-->>Floci: AWS_COGNITO_ENDPOINT が設定されていれば Floci に向く
Docker Compose で floci サービスを足す
compose.yml には次のような floci サービスを足しています(要点のみ)。
services: floci: image: floci/floci:1.5.11 container_name: hoicil-floci ports: - "14566:4566" environment: FLOCI_STORAGE_MODE: hybrid FLOCI_DEFAULT_REGION: ap-northeast-1 volumes: - floci-data:/app/data
ホストの 14566 番に Floci のAPIエンドポイント(コンテナ内部では 4566)を公開しています。FLOCI_STORAGE_MODE=hybrid を指定することで、UserPoolなどの状態がコンテナ再起動を跨いで永続化されるので、 docker compose down〜up のたびにユーザーを作り直さずに済みます。
setup-floci.sh で User Pool / Client を作る
Floci を起動しただけでは中身は空っぽなので、ホイシル用のCognito User Pool / Client を1回作っておく必要があります。これを scripts/setup-floci.sh がやっています。スクリプトのキモを簡略化すると次の通りです。
AWS_CMD="aws --endpoint-url=http://localhost:14566"
# 既に作成済みならスキップ(冪等)
EXISTING=$($AWS_CMD cognito-idp list-user-pools --max-results 10 \
--query 'UserPools[?Name==`hoicil-local`].Id' --output text)
if [ -z "$EXISTING" ]; then
$AWS_CMD cognito-idp create-user-pool --pool-name "hoicil-local" ...
$AWS_CMD cognito-idp create-user-pool-client \
--user-pool-id "$USER_POOL_ID" \
--client-name "hoicil-local-client" \
--generate-secret \
--explicit-auth-flows ALLOW_ADMIN_USER_PASSWORD_AUTH ALLOW_CUSTOM_AUTH ...
fi
# Laravelが読み取る.env に UserPoolId / ClientId / ClientSecret を書き戻す
sed -i.bak "s|^AWS_COGNITO_USER_POOL_ID=.*|AWS_COGNITO_USER_POOL_ID=$USER_POOL_ID|" backend/.env
# (ClientId / ClientSecret も同様に上書き)
make setup-floci でこのスクリプトが、make reset-floci で「コンテナごとボリュームを落として作り直す」が呼ばれるようになっています。
余談: 本当はFloci側のinitialization-hooksを使うべきかも
Flociには initialization-hooks という仕組みがあり、
/etc/floci/init/start.d/配下にスクリプトを置いておくと、Flociが起動してAPIが受け付け可能になった直後に自動実行してくれます。User Pool の作成までであればこちらに乗せられるはずで、いまのsetup-floci.shはその点では素朴な構成になっています。とはいえUserPool IDなどをホスト側のbackend/.envに書き戻す処理がどうしても必要になるので、完全にhook内で閉じることはできないという事情もあり、現状の形に落ち着いています。今後の改善ポイントの一つです。
Laravel 側でFlociに向ける
バックエンド(Laravel)側では、AWS_COGNITO_ENDPOINT 環境変数が設定されている、かつローカル環境のときだけ Flociに向く実装にしています。Cognitoクライアントのコンストラクタの該当部分を抜粋すると次の通りです。
public function __construct() { $config = [ 'version' => env('AWS_SDK_VERSION'), 'region' => env('AWS_DEFAULT_REGION'), ]; $endpoint = env('AWS_COGNITO_ENDPOINT'); $this->useEmulator = app()->isLocal() && $endpoint; if ($this->useEmulator) { $config['endpoint'] = $endpoint; $config['credentials'] = [ 'key' => env('AWS_COGNITO_ACCESS_KEY_ID'), 'secret' => env('AWS_COGNITO_SECRET_ACCESS_KEY'), ]; } $this->aws_cognito_client = new CognitoIdentityProviderClient($config); }
ポイントは app()->isLocal() を AND 条件にしている ところです。AWS_COGNITO_ENDPOINT の付け忘れや誤設定で本番デプロイ時にエミュレータへ向かってしまう、というのを構造的に防ぐためのガードになっています。ローカル以外の環境(staging / production)では、AWS_COGNITO_ENDPOINT をどう設定していても本物のAWSに向かいます。
不足機能とPull Request
先に候補比較で触れた通り、評価時点の Floci v1.5.10 では AdminRespondToAuthChallenge だけが未実装でした。この1操作については、既存の RespondToAuthChallenge の実装から派生させれば書けそうだとコードを読んで判断できていたので、自分で実装してupstreamに投げることにしました。
PR #666 として2026年4月30日に投げたところ、翌日(2026年5月1日)にメンテナの方からレビューがついてそのままmergeされ、続けてリリースされた v1.5.11 に取り込まれました。今回の導入では「足りない機能を自分たちで埋めるつもりではある」と覚悟していたわけですが、実際にはこの1日でホイシル側のスケジュールに影響を出さずに解消できたことになります。メンテナのレスポンスの早さに助けられました。
導入で得られたもの
導入の話はここまでで、最後に課題として挙げた3点が実際にどう解消されたかを振り返っておきます。
① 開発者間でのリソース干渉・データ汚染が起きなくなった
今回のFloci導入で、Cognitoについてはローカル環境を開発者ごとのマシンに閉じ込めることができました。共有のUser Poolを複数人で同時に触ってしまう構図そのものが無くなり、誰かの作業を気にせず並行で別のシナリオを試せるようになっています。テストユーザーを必要なだけ作って、終わったら捨てる、という割り切りもできるようになりました。
なお、S3については今回のFloci化のスコープに含めていません。S3のローカルモックはLocalStackやminioなど枯れている選択肢が複数あるため、まずは見通しの立ちにくかったCognitoから着手したという順序の話で、S3についても別途進めていく予定です。詳しくは最後の「さいごに」で改めて触れます。
② CIでe2eを回す土台ができた
今回の導入で CI(GitHub Actions)でe2eを動かす際にAWSに繋ぐ必要がなくなったので、OIDC連携の整備やCI用IAMユーザーをどう作るかといった議論から解放されました。Flociコンテナが軽量で起動も速いため、CIのジョブのなかで立てて潰してを繰り返しても大きな負担にはなりません。
そしてもう一つ大きいのが、環境がおかしくなったらコンテナごと落として作り直せばよくなったことです。 docker compose down --volumes してから docker compose up し直すだけで、ローカルでもCIでも毎回まっさらで冪等な環境を再現できるようになりました。本物のAWSにつないでいるとリソースの後始末も含めてどうしても運用が必要だった部分が、「コンテナを落として上げ直すだけ」という最小単位で済むのは、CIで安定運用するうえでもとても効いています。
③ e2eテストを「いま書いたコードの直後」に呼べるようになった
そして個人的に一番手応えがあったのが、AI駆動コーディングとe2eテストの距離が一気に縮まったことです。Claude Code などで実装を一周書いた直後に、認証込みのe2eシナリオをローカルでそのまま実行して検証できるので、「書いた → 動かす → 修正する」のループが一段速く回るようになりました。以前は「e2eテストは後でまとめて回しておこう」となりがちだった作業が、生成と検証が同じテンポで噛み合う状態になっています。AIで開発速度を上げたいなら、その後段のフィードバックループ(=e2eテスト)も同じ速度で回せる必要がある、というのは今回の導入を通じて実感したところです。
リスクと対策
最後に、採用にあたって意識しているリスクと、それに対するチームの方針も共有しておきます。
開発サイクルが極めて速い
Flociは公開からおよそ3ヶ月で、執筆時点で累計505件のPull Request、うち458件がmerge済みという勢いで開発されています。直近1週間に絞っても40件のPRがmergeされており、平均で1日5〜6PRがmainに入り続けている計算です。リリースのほうも執筆時点で34リリース、平均で週2〜3リリースのペースになります。
開発が活発であること自体はOSSとしてはむしろ良い兆候ですが、これだけの速度で動いていると、個々のPRに対するコードレビューがどこまで丁寧に行われているかは外から測りにくいのも事実です。サプライチェーン攻撃のリスクが騒がれている昨今、レビューの厚みが外から把握しづらいOSSを取り込む際には、それなりに気を遣う必要があります。
リスクへの対策
このスピード感とどう付き合うか、現状は以下のような運用にしています。
- バージョンを完全に固定する。
floci/floci:1.5.11のようにタグで pin し、新リリースは内容を確認してから手動で上げる。latest系のタグは使わない - 本番では一切使わない、ローカル開発とCIだけに閉じる。運用上の境界を明確に引き、万が一どこかのリリースに問題が混入しても、影響範囲を「e2eテストが失敗する」「ローカル開発が止まる」までに留めて、ユーザーへ影響しない設計にしておく
- Flociはコンテナ内に隔離して、ホスト権限を渡さない運用にする。Flociは Lambda・ECS・EKS・CodeBuild など「実コンテナを起こすタイプのサービス」を使う構成では、ホストのDockerソケット(
/var/run/docker.sock)のマウントを求めることがあります。ただし、Dockerソケットを渡すことは、コンテナ内プロセスにホスト側のDocker daemonに対するroot同等の権限を渡すのに等しい挙動です。これは上記のローカル/CI限定スコープからはみ出してしまうため、避けたい構成になります。今回はCognito用途に限定しているのでこのマウントは行わずに済んでいます。将来、Lambdaなどコンテナにプロキシするタイプのサービスを増やすときはセキュアに動かす方法を考える必要があります
OSSを利用する以上ある程度のリスクは引き受ける前提でしかないのですが、「使うのはローカルとCIだけ・ホスト権限は渡さない」というスコープを守れている限り、大きな影響にはつながりにくいように建付けています。
さいごに
ここまで、ローカル開発環境でAWSのエミュレータとしてFlociを導入した背景と理由、Flociの特徴やどのようにセットアップしたかを説明してきました。
さいごに、今回の導入をふまえて「次に / いずれやりたい」と思っているテーマをいくつか並べておきます。
- Cognito以外のサービスもFlociに寄せていく。S3などはおそらくFlociで素直に動かせる範囲のはずで、順次やっていこうと考えています
setup-floci.shを Floci の initialization-hooks に寄せる。User Pool の作成をコンテナ起動時のhookに移せば、ホスト側のセットアップ手順をもう少し薄くできそうです- Smithy /
smithy-java周りの動きをウォッチする。 floci-io/floci#156 の続報や、Floci側がプロトコル実装をどう仕様駆動に寄せていくのかは、OSSとしての持続性を評価するうえでも引き続き見ていきたいテーマです - 自社で踏んだ不足機能はPRで還元していく。今回 PR #666 で一度サイクルを回せたので、「足りないものは投げる」というスタンスを継続して、ホイシルだけでなくFlociコミュニティ全体が一緒に育っていくようにしていきたいと思っています
長くなりましたが、ホイシルにFlociを導入した話は以上です。
AWS開発でローカル環境にお悩みの方の参考になれば幸いです。ありがとうございました。
-
moto v5.2.1(2026-05-10 リリース、コミット
543c687a)を参照しています。↩ -
実装としては
RdsService.javaがCreateDBInstanceの経路でコンテナ起動とプロキシ起動を担い、RdsAuthProxy.javaがServerSocketで TCP リスナーを開いてvirtual threadで accept ループを回し、エンジン別のMySqlProtocolHandler.java/PostgresProtocolHandler.javaが認証ハンドシェイクを処理しています。↩