Floci × S3 ハンズオン — ローカルでS3互換エミュレータを使い倒す【2026年版】

Floci × S3 ハンズオン — ローカルでS3互換エミュレータを使い倒す【2026年版】
目次

この記事でわかること

  • FlociでS3を使うためのdocker-compose最小構成
  • 各言語SDK(Python / Node.js / Java)での接続例
  • バケット操作・オブジェクト操作・署名URL生成
  • Object Lock などのステートフル機能
  • path-styleなどはまりやすいポイント

Floci は S3 をステートフルに再現する数少ない軽量エミュレータのひとつで、Object Lock・マルチパートアップロード・署名URLまで対応しています。本記事では実装例を中心にハンズオン形式で解説します。

Flociの基本は Floci入門記事 を参照してください。


セットアップ — docker-compose

services:
  floci:
    image: hectorvent/floci:latest
    ports:
      - "4566:4566"
    environment:
      - FLOCI_STORAGE_MODE=persistent
      - FLOCI_STORAGE_PERSISTENT_PATH=/app/data
    volumes:
      - ./floci-data:/app/data
docker compose up -d
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

AWS CLIでの基本操作

# バケット作成
aws s3 mb s3://my-bucket

# オブジェクトアップロード
echo "hello" > hello.txt
aws s3 cp hello.txt s3://my-bucket/

# 一覧
aws s3 ls s3://my-bucket/

# ダウンロード
aws s3 cp s3://my-bucket/hello.txt ./downloaded.txt

# 削除
aws s3 rm s3://my-bucket/hello.txt
aws s3 rb s3://my-bucket --force

Python(boto3)実装例

クライアント初期化

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:4566",
    region_name="us-east-1",
    aws_access_key_id="test",
    aws_secret_access_key="test",
    config=boto3.session.Config(s3={"addressing_style": "path"}),
)

基本操作

# バケット作成
s3.create_bucket(Bucket="my-bucket")

# アップロード
s3.put_object(Bucket="my-bucket", Key="sample.txt", Body=b"Hello Floci")

# 取得
obj = s3.get_object(Bucket="my-bucket", Key="sample.txt")
print(obj["Body"].read().decode())

# 一覧
for obj in s3.list_objects_v2(Bucket="my-bucket").get("Contents", []):
    print(obj["Key"], obj["Size"])

# 削除
s3.delete_object(Bucket="my-bucket", Key="sample.txt")

署名URL(Presigned URL)

url = s3.generate_presigned_url(
    "get_object",
    Params={"Bucket": "my-bucket", "Key": "sample.txt"},
    ExpiresIn=3600,
)
print(url)  # http://localhost:4566/my-bucket/sample.txt?... で取得可能

Node.js(AWS SDK v3)実装例

import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  endpoint: "http://localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
  forcePathStyle: true,
});

// アップロード
await s3.send(new PutObjectCommand({
  Bucket: "my-bucket",
  Key: "sample.txt",
  Body: "Hello Floci",
}));

// 署名URL
const url = await getSignedUrl(
  s3,
  new GetObjectCommand({ Bucket: "my-bucket", Key: "sample.txt" }),
  { expiresIn: 3600 }
);
console.log(url);

Java(AWS SDK v2)実装例

S3Client s3 = S3Client.builder()
    .endpointOverride(URI.create("http://localhost:4566"))
    .region(Region.US_EAST_1)
    .credentialsProvider(StaticCredentialsProvider.create(
        AwsBasicCredentials.create("test", "test")))
    .forcePathStyle(true)
    .build();

s3.createBucket(b -> b.bucket("my-bucket"));
s3.putObject(b -> b.bucket("my-bucket").key("sample.txt"),
             RequestBody.fromString("Hello Floci"));

マルチパートアップロード

大きなファイルを分割アップロードする例(boto3):

s3.upload_file(
    Filename="./large-video.mp4",
    Bucket="my-bucket",
    Key="videos/large-video.mp4",
    Config=boto3.s3.transfer.TransferConfig(
        multipart_threshold=8 * 1024 * 1024,  # 8MB以上は分割
        multipart_chunksize=8 * 1024 * 1024,
    ),
)

Flociはマルチパートアップロードの分割・結合処理に対応しています。


Object Lock(WORM保護)

コンプライアンス用途で不変オブジェクトを保存するObject Lockも利用可能:

s3.create_bucket(
    Bucket="locked-bucket",
    ObjectLockEnabledForBucket=True,
)

s3.put_object(
    Bucket="locked-bucket",
    Key="immutable.txt",
    Body=b"cannot be deleted",
    ObjectLockMode="COMPLIANCE",
    ObjectLockRetainUntilDate=datetime(2030, 1, 1),
)

よくあるエラーと対処

PermanentRedirect / BucketRegionError

原因: virtual-hosted styleでアクセスしている(my-bucket.localhost:4566)。 対処: SDKで forcePathStyle: true / s3_use_path_style=true / addressing_style="path" を設定。

SignatureDoesNotMatch

原因: リージョンやアクセスキーの不一致。 対処: AWS_DEFAULT_REGION=us-east-1、キーは test で統一。

NoSuchBucket

原因: memory モードで再起動した後、バケットが消えた。 対処: FLOCI_STORAGE_MODE=persistent に変更、またはseedスクリプトで自動作成。

アプリコンテナから署名URLが localhost を返す

原因: FLOCI_BASE_URL または FLOCI_HOSTNAME が未設定。 対処: Docker Compose内では FLOCI_HOSTNAME=floci を設定。


FAQ

Q. 署名URLはブラウザからも使える?

FLOCI_BASE_URLhttp://localhost:4566 に設定すればホストマシンのブラウザから直接DL可能です。

Q. S3イベント通知(SNS/SQS連携)は動く?

はい。Flociは s3:ObjectCreated:* などのイベントでSQS/SNS連携に対応しています。

Q. バケットポリシーやACLの検証は?

基本的なポリシー構文は評価されますが、本番のIAM統合ほど厳密ではありません。権限の最終検証は実AWSで行ってください。

Q. S3 Select や Glacier は?

S3 Select は未対応。Glacierはバケットタグのみ対応し、実体のアーカイブ機構はありません。


ユースケース別の実装例

1. 画像アップロード(Web/モバイルアプリ)

ユーザーがブラウザから直接アップロードする典型パターン:

# バックエンド: 署名URL発行
upload_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "user-uploads",
        "Key": f"avatar/{user_id}.jpg",
        "ContentType": "image/jpeg",
    },
    ExpiresIn=600,
)
return {"uploadUrl": upload_url}
// フロントエンド: 署名URLに直接PUT
await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": "image/jpeg" },
  body: file,
});

バックエンドを経由せずフロントから直接S3にアップロードするため、サーバー負荷を抑えられます。

2. アプリケーションログ保管

バッチで日次ログをS3にアーカイブ:

from datetime import date
key = f"logs/{date.today().isoformat()}/app.log.gz"
with open("/var/log/app.log.gz", "rb") as f:
    s3.put_object(Bucket="app-logs", Key=key, Body=f, ContentEncoding="gzip")

ライフサイクルポリシー(後述)で 30日後に削除 などを組み合わせると実運用に近づけられます。

3. 静的サイト配信

S3 + CloudFront 静的サイトのS3側挙動をローカルで検証:

aws s3 website s3://my-site --index-document index.html --error-document 404.html \
  --endpoint-url http://localhost:4566

curl http://localhost:4566/my-site/index.html

CORS 設定

フロントから直接署名URLを叩く場合はCORS設定が必須:

s3.put_bucket_cors(
    Bucket="user-uploads",
    CORSConfiguration={
        "CORSRules": [{
            "AllowedOrigins": ["http://localhost:3000"],
            "AllowedMethods": ["GET", "PUT", "POST"],
            "AllowedHeaders": ["*"],
            "ExposeHeaders": ["ETag"],
            "MaxAgeSeconds": 3000,
        }]
    },
)

本番と同様にOPTIONSプリフライトリクエストの挙動を確認できます。


バージョニング

s3.put_bucket_versioning(
    Bucket="my-bucket",
    VersioningConfiguration={"Status": "Enabled"},
)

# 複数バージョンの作成
s3.put_object(Bucket="my-bucket", Key="doc.txt", Body=b"v1")
s3.put_object(Bucket="my-bucket", Key="doc.txt", Body=b"v2")

# 旧バージョンの取得
versions = s3.list_object_versions(Bucket="my-bucket", Prefix="doc.txt")
for v in versions["Versions"]:
    print(v["VersionId"], v["IsLatest"])

誤削除リカバリのロジックをローカルで安全に検証できます。


ライフサイクルルール

s3.put_bucket_lifecycle_configuration(
    Bucket="app-logs",
    LifecycleConfiguration={
        "Rules": [{
            "ID": "delete-old-logs",
            "Status": "Enabled",
            "Prefix": "logs/",
            "Expiration": {"Days": 30},
        }]
    },
)

ルール設定の構文エラーがないか、対象プレフィックスが正しく指定されているかを開発中に確認できます。


S3イベント → Lambda/SQS 連携

# SQSキュー作成
sqs = boto3.client("sqs", endpoint_url="http://localhost:4566", region_name="us-east-1",
                   aws_access_key_id="test", aws_secret_access_key="test")
q = sqs.create_queue(QueueName="s3-events")["QueueUrl"]
arn = sqs.get_queue_attributes(QueueUrl=q, AttributeNames=["QueueArn"])["Attributes"]["QueueArn"]

# S3 → SQS通知
s3.put_bucket_notification_configuration(
    Bucket="user-uploads",
    NotificationConfiguration={
        "QueueConfigurations": [{
            "QueueArn": arn,
            "Events": ["s3:ObjectCreated:*"],
        }]
    },
)

# アップロード
s3.put_object(Bucket="user-uploads", Key="test.png", Body=b"x")

# メッセージ受信
msg = sqs.receive_message(QueueUrl=q, WaitTimeSeconds=3)
print(msg.get("Messages"))

パフォーマンスTips

  • 大量並行アップロード には TransferConfig(max_concurrency=N) を利用
  • メモリ使用量 が気になるならストリーミングAPI(upload_fileobj)を使う
  • Floci側は FLOCI_STORAGE_MODE=hybrid で高速化と永続化を両立
  • ローカルSSD性能に依存するため、Docker Desktopのディスク上限を拡張しておく

本番S3との差異チェックリスト

  • バケットポリシーの厳密な評価(aws:SourceIpなど)
  • Intelligent-Tiering / Glacier などのストレージクラス
  • Object Lambda / Access Points
  • イベント通知の遅延特性
  • S3 Select / バッチオペレーション
  • レプリケーション(CRR/SRR)

上記はFlociで構文は受け付けるが挙動は簡略化。最終確認は実AWSで。


まとめ

  • FlociはS3のステートフル動作を軽量に再現できる
  • path-style(forcePathStyle)と FLOCI_HOSTNAME の設定が初回の鬼門
  • Object Lock・マルチパート・署名URLまで対応し、ローカル開発の大半をカバー
  • 本番との差異(ACL/ポリシー詳細・Glacier) に注意

関連記事