TL;DR
- ローカル開発と CI で S3 の代わりに使っていた MinIO の公式イメージ(Docker Hub の
minio/minioとminio/mc)が、ある日 404 になりました - その日から、全 PR の統合テストが
pull access deniedで失敗しました。quay.io のイメージも匿名では pull できません - 後継を探す前に、バッチが S3 に求める機能を洗い出しました。使う API は 5 つだけで、あとは path-style のアクセスと、
mcによる初期化です - 8 つの選択肢を比べ、最後の 2 つ(MinIO のコミュニティフォークと RustFS)は実機で統合テストを流して比べました
- 採用したのはフォークの
pgsty/minioです。変更がイメージ名だけで済み、CI をすぐ戻せるからです。代わりに、タグとダイジェストの両方でイメージを固定し、配布が止まったときの切り替え先(RustFS)も先に決めました
この記事の対象読者
- ローカル開発や CI で MinIO を S3 の代わりに使っている方
- 使っていた OSS のイメージが突然消えて、後継を探している方
- コミュニティのフォークに依存するときのリスクを、どう抑えるか考えている方
環境
- Go(AWS SDK for Go v2)で書いたバッチ。本番は Amazon S3、ローカルと CI は S3 互換のストレージ
- Docker Compose(ローカル開発用とテスト用の 2 つ)
- GitHub Actions(PR ごとに統合テストを実行)
- 比較したイメージ:
pgsty/minio(RELEASE.2026-08-04T00-00-00Z)、rustfs/rustfs(1.0.0)
背景:ある日、全 PR の統合テストが落ちた
テナント単位でデータを整理するバッチを開発しています。このバッチは、本番では Amazon S3 を使います。ローカル開発と CI では AWS に接続せず、S3 互換の MinIO で代わりをさせていました。同じ S3 の API で、再現性のある検証をするためです。
ある日を境に、全 PR の統合テストが次のエラーで落ちるようになりました。
pull access denied for minio/minio, repository does not exist or may require 'docker login'
Docker Hub の minio/minio と minio/mc が、どちらも 404 になっていました。最後に統合テストが通ったのは 2026-09-09 で、2026-09-16 以降はすべて失敗していました。
別の配布先の quay.io にも同じ名前のイメージはありますが、匿名では pull できません(401)。同じイメージを使うローカル開発の環境も、新しく作れなくなっていました。
後継を探す前に、要件を絞った
最初にやったのは、代わりのイメージを探すことではありませんでした。バッチが S3 に何を求めているかを洗い出すことです。
MinIO は S3 の多くの API を実装しています。しかしバッチが使っているのは、そのごく一部でした。
| 要件 | 中身 |
|---|---|
| 使う S3 の API |
PutObject / ListObjectsV2 / DeleteObject / DeleteObjects / HeadBucket の 5 つ |
| アクセスの方式 | path-style(SDK の設定で UsePathStyle = true) |
| 起動時の初期化 | バケットの作成と初期データの投入。MinIO のクライアント mc を使うスクリプトがある |
| ローカル開発で欲しいもの | データの永続化と、中身を確かめられる GUI |
要件が分かると、比べる観点がはっきりします。「MinIO と同じくらい高機能か」ではなく、「この 5 つの API と path-style と初期化が動くか」を見ればよいからです。
8 つの選択肢を比べた
| 選択肢 | 概要 | 評価 |
|---|---|---|
| pgsty/minio + pgsty/mc | MinIO のコミュニティフォーク(pgsty/silo、AGPL-3.0)が公開するイメージ | 採用 |
| RustFS | Rust 製の S3 互換ストレージ(Apache-2.0)。1.0.0 の GA は 2026-09-16 | 代替候補の第一位 |
| adobe/s3mock | テスト用の S3 互換サーバー(Apache-2.0) | CI には十分。ただし GUI が無く、再起動をまたいだデータの再利用は公式にはサポートされない |
| SeaweedFS | Go 製の分散ファイルシステム(Apache-2.0) | 使える見込み。ただし固定のアクセスキーに、S3 用の設定ファイル(JSON)が別に要る(未検証) |
| Garage | Rust 製の分散ストレージ(AGPL-3.0) | 起動後にクラスタの構成の適用とキーの作成が要り、テスト環境の初期化が複雑になる(未検証) |
| Ceph RGW | 大規模向けの統合ストレージ | 最低 3 ノードが要り、用途に対して過剰 |
| MinIO をソースからビルド | 公式のソースから自前でイメージを作る | CI が遅くなる。上流の保守の状況も不透明 |
| MinIO の有償版・認証付きのレジストリ | 公式の配布物を、認証付きで取得する | 契約と認証情報の管理が要る |
テスト専用のモックや分散ストレージは、どれも「CI だけなら動く」か「動くが設定が増える」でした。ローカル開発の GUI と永続化まで満たし、初期化の手順も変えずに済みそうなのは、上の 2 つだけでした。
最後の 2 つは、実機で統合テストを流して比べた
pgsty/minio と RustFS は、どちらも資料の上では要件を満たします。そこで、CI と同じ手順で実際に動かしました。ボリュームを作り直したまっさらな状態でテスト用の Compose を起動し、統合テストを実行します。
| 観点 | pgsty/minio | RustFS |
|---|---|---|
| 統合テスト | 通った | 通った |
既存の mc の初期化スクリプト |
そのまま動いた | そのまま動いた(クライアントは pgsty/mc を使用) |
| Web の GUI | あり。従来の MinIO Console と同じ | あり(/rustfs/console/) |
| データの永続化 | あり。従来どおりボリューム | あり。ただしコンテナは UID 10001 で動くので、bind mount では権限に注意 |
| 必要な変更 | イメージ名だけ | イメージ名、起動コマンド、認証の環境変数、GUI の URL |
| 挙動の差 | 無い(MinIO と同じコード) | 独自の実装なので、細部が MinIO と違う可能性がある |
| ライセンス | AGPL-3.0 | Apache-2.0 |
| 持続性 | 小規模な組織のフォーク(GitHub のスター約 4,000)。Docker Hub のイメージは 2026-08-04 以降更新が無い | 大規模なコミュニティ(GitHub のスター約 34,600)。更新が活発 |
| 成熟度 | MinIO の実績を引き継ぐ | GA から約 3 週間 |
どちらも要件は満たしました。差が出たのは「変更の量」と「挙動が変わるリスク」、そして「持続性」です。
ライセンスも確かめました。AGPL-3.0 は、社内のローカル環境と CI で使うだけなら配布にあたらないので、影響は小さいと判断しています。もともとの公式の MinIO も AGPL-3.0 なので、条件は従来と変わりません。
決定:フォークを採用し、RustFS を切り替え先に決めておく
採用したのは pgsty/minio と pgsty/mc です。理由は 2 つです。
-
変更がイメージ名だけで済む。 GUI も永続化も
mcの手順もドキュメントも、何も変えずに CI をすぐ戻せます - 挙動が変わらない。 RustFS も実機で要件を満たしましたが、GA から日が浅く、MinIO との細かな挙動の差を見極める時間がありません。今回の目的は CI を早く戻すことなので、挙動が変わらない選択肢を優先しました
一方で、持続性は RustFS の方が上です。そこで、配布が止まったときの切り替え先を、今のうちに決めておきました。
- フォークの配布が止まったら、または新しい版が Docker Hub に出なくなったら、RustFS に切り替える
- 手順は、Compose のサーバーを
rustfs/rustfsに替え、起動コマンドと認証の環境変数を設定するだけ。初期化は引き続きmcを使える - RustFS が安定してきたら、障害が起きる前に移ることも検討する
実機で比べておいたおかげで、切り替えの手順まで具体的に書けています。「いざとなったら RustFS」ではなく、何をどう変えればよいかが分かっている状態です。
フォークに依存するリスクを、イメージの固定で抑える
小規模な組織が配布するイメージに依存すると、新しいリスクが生まれます。配布元のアカウントが乗っ取られるなどして、同じタグのまま中身が差し替えられるリスクです。
そこで、イメージをタグとダイジェストの両方で固定しました。
services:
test-minio:
image: pgsty/minio:RELEASE.2026-08-04T00-00-00Z@sha256:b6bfe723…
test-minio-init:
image: pgsty/mc:RELEASE.2026-09-16T00-00-00Z@sha256:cfc83108…
ダイジェスト(@sha256:…)は、イメージの中身から計算される値です。中身が差し替えられるとダイジェストが変わるので、一致しないイメージは pull の時点で失敗します。タグも残しているのは、人が読んだときにどの版か分かるようにするためです。
版を上げるときは、依存の更新を自動で PR にするツール(Renovate)の PR を、レビューしてから取り込みます。ダイジェストが変わる更新も、人の目を通ります。
同じ考え方で、GitHub Actions もコミット SHA で固定した
同じ時期に、GitHub Actions の uses: も、タグからコミット SHA に固定しました。
# 変更前
- uses: actions/checkout@v7
# 変更後
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
タグは、配布元が後から別のコミットに付け替えられます。コミット SHA なら、中身が変わりません。末尾のコメントに版を書いておけば、人が読んでも分かり、Renovate もそのコメントを更新してくれます。
依存しているものの中身が、知らないうちに変わらないようにする。イメージのダイジェストと Actions の SHA は、どちらもこのための仕組みです。
はまったところ:古いコンテナが名前でぶつかる
ローカルに、旧イメージで作ったコンテナが残っていると、新しい Compose が起動できません。コンテナ名が同じでぶつかるためです。
docker rm minio minio-init
docker compose up
古いコンテナを消してから起動し直せば解決します。この手順は、判断の記録(ADR)に書いておきました。
まとめ
| やったこと | 理由 |
|---|---|
| 後継を探す前に、使う S3 の機能を洗い出した | 比べる観点が「この 5 つが動くか」に絞れる |
| 8 つの選択肢を、要件に照らして比べた | モックや分散ストレージは、ローカル開発の体験か設定の量で落ちた |
| 最後の 2 つは、CI と同じ手順で実機で比べた | 資料の上では両方満たす。差は動かして初めて分かる |
| 変更が最小で挙動が変わらないフォークを採用した | 目的は CI を早く戻すこと |
| 配布が止まったときの切り替え先(RustFS)を先に決めた | 持続性はフォークより上。手順まで書いておける |
| イメージをタグとダイジェストで、Actions をコミット SHA で固定した | フォークへの依存で増えた、中身の差し替えのリスクを抑える |
学びは 2 つです。
- 代わりを探す前に、何が要るかを絞る。 「MinIO の代わり」を探すと、MinIO と同じくらい高機能なものを探してしまいます。実際に要ったのは、5 つの API と path-style と初期化だけでした
- 急ぎで選んだものほど、逃げ道を先に決める。 早く戻すために選んだ選択肢には、たいてい持続性の弱さがあります。固定でリスクを抑え、切り替え先と手順を先に書いておけば、次に止まったときに慌てずに済みます