MinioからLocalStackに乗り換えました
こんにちは!RemitAidでソフトウェアエンジニアをしているnos0611です。
はじめに
RemitAidではローカル開発環境でS3互換ストレージとしてMinioを使用していたのですが、この度LocalStackに乗り換えました。
本記事ではその背景と対応内容についてご紹介します。
なぜ乗り換えたのか?
2025年10月、MinioがDockerイメージの無料配布を終了するというニュースが流れました。
これにより、今後ローカル開発環境でMinioを使い続けることが難しくなる可能性が出てきたため、S3のローカルエミュレーションをLocalStackに移行することにしました。
LocalStackとは
LocalStackは、AWSのクラウドサービスをローカル環境でエミュレートするツールです。
S3だけでなく、SQS、Lambda、DynamoDBなど多くのサービスに対応しており、ローカルでAWSライクな開発・テストが可能になります。
単にS3互換というだけであれば他にも選択肢はありましたが、SQSやLambdaについてもローカルで構築する予定があったためLocalStackを選択しています。
移行作業の内容
※Minio関連の既存リソースについては削除するだけなので割愛しています
1. docker-compose.yamlの変更
LocalStackの設定を追加しました。
localstack:
image: localstack/localstack:3.0
container_name: localstack
ports:
- "4566:4566"
environment:
- SERVICES=s3
- AWS_DEFAULT_REGION=ap-northeast-1
- DEBUG=1
- PERSISTENCE=1
- MAIN_CONTAINER_NAME=localstack
- S3_SKIP_SIGNATURE_VALIDATION=0
volumes:
- ../../data/localstack:/var/lib/localstack
- ./localstack/initialize:/etc/localstack/init/ready.d
各設定値については以下の様な意味合いです(公式ドキュメント)。
| 環境変数 | 説明 |
|---|---|
SERVICES |
LocalStackが起動時にロードするサービスを指定。デフォルトでは全サービスが有効ですが、必要なサービスのみ指定することで起動時間を短縮できるようです。 |
AWS_DEFAULT_REGION |
デフォルトのAWSリージョン。 |
DEBUG |
1に設定すると詳細なログを出力します。 |
PERSISTENCE |
1に設定するとデータの永続化が有効になり、コンテナ再起動後もデータが保持されます。 |
MAIN_CONTAINER_NAME |
Dockerコンテナ名を指定します。 |
S3_SKIP_SIGNATURE_VALIDATION |
S3の署名検証をスキップするかどうかのフラグ。デフォルトは1(スキップ)です。 |
volumesについては以下の通りです。
-
../../data/localstack:/var/lib/localstack: 永続化データの保存先 -
./localstack/initialize:/etc/localstack/init/ready.d: 初期化スクリプトをマウント
2. バケット作成の初期化スクリプト
LocalStackでは/etc/localstack/init/ready.d/配下にスクリプトを配置すると、LocalStack起動時に自動実行されます。
S3バケットの作成とCORS設定を行う初期化スクリプトを用意しました。
init.sh:
#!/bin/bash
echo "Creating S3 buckets..."
# バケット作成(ap-northeast-1リージョン)
awslocal s3api create-bucket \
--bucket my-app-images-local \
--create-bucket-configuration LocationConstraint=ap-northeast-1
# CORS設定
awslocal s3api put-bucket-cors \
--bucket my-app-images-local \
--cors-configuration file:///etc/localstack/init/ready.d/cors_config.json
# 他のバケットも同様に作成...
S3へのダイレクトアップロードなども利用しているためCORSを設定しています。ローカルでの動作確認用のためかなり緩めです。
cors_config.json:
{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "POST", "PUT", "HEAD", "DELETE"],
"AllowedOrigins": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
}
3. 署名付きURLのエンドポイント問題
LocalStackで署名付きURLを使う場合は少し工夫が必要です。
アプリケーションはDockerコンテナ内で動作しているため、S3へのアクセスはhost.docker.internal:4566を使います。
しかし、署名付きURLはブラウザから直接アクセスするため、localhost:4566でないとアクセスできません。
そのため、S3クライアントと署名付きURL生成用のPresignClientで異なるエンドポイントを設定しています。
以下はコードのイメージです。
// 環境ごとの設定を管理する構造体
type clientConfig struct {
endpoint *string // S3クライアント用エンドポイント
presignedEndpoint *string // 署名付きURL用エンドポイント
credentials aws.CredentialsProvider
usePathStyle bool
}
// 環境に応じた設定を返す
func buildClientConfig(isLocal bool) clientConfig {
if isLocal {
// LocalStack用の設定
return clientConfig{
endpoint: aws.String("http://host.docker.internal:4566"),
presignedEndpoint: aws.String("http://localhost:4566"),
credentials: credentials.NewStaticCredentialsProvider("test", "test", ""),
usePathStyle: true,
}
}
// AWS環境用の設定(デフォルト)
return clientConfig{
endpoint: nil,
presignedEndpoint: nil,
credentials: nil,
usePathStyle: false,
}
}
func NewClient(cfg config.Config) Client {
clientCfg := buildClientConfig(cfg.IsLocal)
// AWS SDK設定の読み込み
awsCfg, _ := awsconfig.LoadDefaultConfig(context.Background(),
awsconfig.WithRegion(cfg.Region),
)
// S3クライアント(コンテナ内通信用: host.docker.internal:4566)
s3Client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
if clientCfg.endpoint != nil {
o.BaseEndpoint = clientCfg.endpoint
}
o.UsePathStyle = clientCfg.usePathStyle
})
// PresignClient(ブラウザからのアクセス用: localhost:4566)
var presignOpts []func(*s3.PresignOptions)
if clientCfg.presignedEndpoint != nil {
presignOpts = append(presignOpts, func(po *s3.PresignOptions) {
po.ClientOptions = append(po.ClientOptions, func(o *s3.Options) {
o.BaseEndpoint = clientCfg.presignedEndpoint
o.UsePathStyle = clientCfg.usePathStyle
})
})
}
return &client{
s3Client: s3Client,
presignClient: s3.NewPresignClient(s3Client, presignOpts...),
}
}
ポイントは以下の通りです。
-
s3Client: コンテナ内からLocalStackにアクセスするためhost.docker.internal:4566を使用 -
presignClient: 生成されたURLはブラウザから直接アクセスされるためlocalhost:4566を使用 -
usePathStyle: true: LocalStackはパススタイル(http://localhost:4566/bucket-name)でのアクセスが必要
まとめ
- Minioの無料Dockerイメージ配布終了に伴い、LocalStackに乗り換えました
- LocalStackでは初期化スクリプトでバケット作成やCORS設定が可能です
告知
Podcast 「RemiTalk」では普段の会社の様子を赤裸々に語っています。
もし良ければ聴いてみてください!
Podcast 文字起こしはこちら
Discussion