AWS S3互換の開発環境用S3のスタブとして重宝していたMinIOですが、
先月、突然Dockerイメージの無料配布を止めてしまったので代替えを探している最中です。
▼例の件の紹介記事

▼大炎上したissue(ロックされているのでリアクションすらできません)
筆者的には、Dockerコンテナ構築できて、S3互換の操作(ファイルのCRUD)が出来て、パフォーマンスが良くて、設定しやすくて、扱いやすくて、安定していて、怪しい国の影響がなければ何でも良いです。
▼開発環境用MinIOの代替え候補
あたりがありますが、今回はGarageを使ってみます。
これからやること
- Garage設定ファイル準備
- Garage Dockerコンテナ起動
- Garage Dockerコンテナ:クラスターレイアウト作成
- Garage Dockerコンテナ:バケット作成
- Garage Dockerコンテナ:キー作成
- Garage Dockerコンテナ:バケットへのキー割り当て
- aws cliを使ったファイルのアップロードとダウンロードテスト
- docker-compose.yml 作成とコンテナ構築
- Garage Web UI を使った設定と動作テスト
- Laravel tinker を使った動作テスト
前提条件
- dockerインストール済
- Ubuntu24.04.3 LTS (WSL2 on Windows11) 上で作業していきます
- aws cliを使います(別になくてもLaravelで使えればOK)
- Python3.9以降インストール済(aws cli用)
- GarageのWeb UIはkhairul169/garage-webuiを使います
- Laravel12 + league/flysystem-aws-s3-v3 でテストします
- PHP8.2以降インストール済(Laravel12の要件)
- Composer 2.xインストール済(Laravel要件、phpenvで同梱インストールされます)
▼Pythonインストール
▼PHPのインストール
フォルダ構成
[プロジェクトトップ]
├─ docker/ ・・・Docker用
│ └─ garage/ ・・・Garage用
│ ├─ data/ ・・・オブジェクト保存用(chmod 777)
│ ├─ meta/ ・・・メタデータ保存用(chmod 777)
│ └─ garage.toml ・・・Garage設定ファイル
├─ html/ ・・・Laravelプロジェクト用
└─ docker-compose.yml
※ディレクトリ構成は作成済の前提で作業を進めます。
最初に設定ファイルを準備
公式ドキュメントで紹介されている、「最初に簡単に始めるための最小構成の設定」をファイルで準備します。
シークレットを生成して設定ファイルに埋め込むために、次のコマンドで設定ファイルを生成します。
cat > docker/garage/garage.toml <<EOF
metadata_dir = "/tmp/meta"
data_dir = "/tmp/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "$(openssl rand -hex 32)"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.localhost"
index = "index.html"
[k2v_api]
api_bind_addr = "[::]:3904"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "$(openssl rand -base64 32)"
metrics_token = "$(openssl rand -base64 32)"
EOF
Dockerイメージを起動してみる
後でdocker-compose.ymlを作成しますが、
その前に、Garageの使い方を確認するために、次のコマンドでコンテナを起動してみます。
docker run \
-d \
--name garaged \
-p 3900:3900 -p 3901:3901 -p 3902:3902 -p 3903:3903 \
-v ./docker/garage/garage.toml:/etc/garage.toml \
-v ./docker/garage/meta:/var/lib/garage/meta \
-v ./docker/garage/data:/var/lib/garage/data \
dxflrs/garage:v2.1.0

うわー、zshの修正提案ウザイ。。
「~/.zshrc」に次の行がある場合はコメントアウトか削除しておけば修正されなくなります。
#setopt correct
#setopt correct_all
では、気を取り直して、garagedの状態を確認してみます。
docker exec -it garaged /garage status
※dockerコンテナのデフォルトでは、garageコマンドのパスは /garage になります。

Dockerコンテナのログも確認してみます。
docker logs garaged
2025-11-15 11:27:00.884 | 2025-11-15T02:27:00.884244Z INFO garage::server: Loading configuration...
2025-11-15 11:27:00.885 | 2025-11-15T02:27:00.885183Z INFO garage::server: Initializing Garage main data store...
2025-11-15 11:27:00.886 | 2025-11-15T02:27:00.886061Z INFO garage_model::garage: Opening database...
2025-11-15 11:27:00.886 | 2025-11-15T02:27:00.886135Z INFO garage_db::sqlite_adapter: Opening Sqlite database at: /tmp/meta/db.sqlite
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.288994Z INFO garage_model::garage: Initializing RPC...
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289092Z INFO garage_model::garage: Initialize background variable system...
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289100Z INFO garage_model::garage: Initialize membership management system...
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289140Z INFO garage_rpc::system: Generating new node key pair.
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289355Z INFO garage_rpc::system: Node ID of this node: 1ea44a6a247a66b2
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289470Z INFO garage_rpc::layout::manager: No valid previous cluster layout stored (IO error: No such file or directory (os error 2)), starting fresh.
2025-11-15 11:27:01.289 | 2025-11-15T02:27:01.289597Z INFO garage_rpc::layout::helper: ack_until updated to 0
2025-11-15 11:27:01.290 | 2025-11-15T02:27:01.289858Z INFO garage_model::garage: Initialize block manager...
2025-11-15 11:27:01.290 | 2025-11-15T02:27:01.290744Z INFO garage_model::garage: Initialize admin_token_table...
2025-11-15 11:27:01.291 | 2025-11-15T02:27:01.291421Z INFO garage_model::garage: Initialize bucket_table...
2025-11-15 11:27:01.292 | 2025-11-15T02:27:01.292048Z INFO garage_model::garage: Initialize bucket_alias_table...
2025-11-15 11:27:01.292 | 2025-11-15T02:27:01.292734Z INFO garage_model::garage: Initialize key_table_table...
2025-11-15 11:27:01.293 | 2025-11-15T02:27:01.293408Z INFO garage_model::garage: Initialize block_ref_table...
2025-11-15 11:27:01.294 | 2025-11-15T02:27:01.294109Z INFO garage_model::garage: Initialize version_table...
2025-11-15 11:27:01.294 | 2025-11-15T02:27:01.294752Z INFO garage_model::garage: Initialize multipart upload counter table...
2025-11-15 11:27:01.295 | 2025-11-15T02:27:01.295533Z INFO garage_model::garage: Initialize multipart upload table...
2025-11-15 11:27:01.296 | 2025-11-15T02:27:01.296352Z INFO garage_model::garage: Initialize object counter table...
2025-11-15 11:27:01.297 | 2025-11-15T02:27:01.297136Z INFO garage_model::garage: Initialize object_table...
2025-11-15 11:27:01.298 | 2025-11-15T02:27:01.297994Z INFO garage_model::garage: Load lifecycle worker state...
2025-11-15 11:27:01.298 | 2025-11-15T02:27:01.298045Z INFO garage_model::garage: Initialize K2V counter table...
2025-11-15 11:27:01.299 | 2025-11-15T02:27:01.298934Z INFO garage_model::garage: Initialize K2V subscription manager...
2025-11-15 11:27:01.299 | 2025-11-15T02:27:01.298955Z INFO garage_model::garage: Initialize K2V item table...
2025-11-15 11:27:01.299 | 2025-11-15T02:27:01.299752Z INFO garage_model::garage: Initialize K2V RPC handler...
2025-11-15 11:27:01.300 | 2025-11-15T02:27:01.299913Z INFO garage::server: Initializing background runner...
2025-11-15 11:27:01.300 | 2025-11-15T02:27:01.299994Z INFO garage::server: Spawning Garage workers...
2025-11-15 11:27:01.300 | 2025-11-15T02:27:01.300248Z INFO garage_model::s3::lifecycle_worker: Starting lifecycle worker for 2025-11-15
2025-11-15 11:27:01.300 | 2025-11-15T02:27:01.300277Z INFO garage::server: Initialize Admin API server and metrics collector...
2025-11-15 11:27:01.304 | 2025-11-15T02:27:01.304458Z INFO garage_model::s3::lifecycle_worker: Lifecycle worker finished for 2025-11-15, objects expired: 0, mpu aborted: 0
2025-11-15 11:27:01.367 | 2025-11-15T02:27:01.367736Z INFO garage::server: Launching internal Garage cluster communications...
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367813Z INFO garage::server: Initializing S3 API server...
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367820Z INFO garage::server: Initializing K2V API server...
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367838Z INFO garage::server: Initializing web server...
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367851Z INFO garage::server: Launching Admin API server...
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367982Z INFO garage_api_common::generic_server: S3 API server listening on http://[::]:3900
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.367997Z INFO garage_net::netapp: Listening on [::]:3901
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.368098Z INFO garage_web::web_server: Web server listening on http://[::]:3902
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.368103Z INFO garage_api_common::generic_server: K2V API server listening on http://[::]:3904
2025-11-15 11:27:01.368 | 2025-11-15T02:27:01.368138Z INFO garage_api_common::generic_server: Admin API server listening on http://[::]:3903
cluster layoutのエラーが出ていますが、cluster layoutはこれから作成していきます。
クラスターレイアウト作成
まず最初に公式ドキュメントを確認してみます。
Garage デプロイメントのクラスタレイアウトを作成するということは、クラスタの各ノードで使用可能なディスク容量 -c と、各マシンが配置されているゾーン (データセンターなど) の名前 -z を Garage に通知することを意味します。
テスト展開では、ゾーンが dc1 という名前で容量が 1G のノードが 1 つしかありませんが、単一ノード展開では容量は無視され、後で新しいノードを追加するときに変更できます。
garage layout assign -z dc1 -c 1G <node_id>ここで、Garage Status (最初の列) で示されるノードの識別子に対応します。その識別子のプレフィックスを入力するだけです。たとえば、ここでは、ガレージレイアウトassign -z dc1 -c 1G 563eを記述できます。
レイアウトは、次のようにしてクラスターに適用する必要があります。
garage layout apply --version 1
クラスターレイアウトを次のようにしてみます。
- ノードID:garage status で確認(左端のID)
- ゾーン: apne1-az1(適当)
- 容量: 1GB
次のコマンドでクラスターレイアウトを作成します。
ノードIDは環境に合わせて書き換えてください。
docker exec -it garaged /garage layout assign -z apne1-az1 -c 1G 1ea44a6a247a66b2

クラスターレイアウトの変更はステージングされている状態なので、
次のコマンドで確認してみます。
$ docker exec -it garaged /garage layout show
2025-11-15T03:01:28.793523Z INFO garage_net::netapp: Connected to 127.0.0.1:3901, negotiating handshake...
2025-11-15T03:01:28.841856Z INFO garage_net::netapp: Connection established to 1ea44a6a247a66b2
==== CURRENT CLUSTER LAYOUT ====
No nodes currently have a role in the cluster.
See `garage status` to view available nodes.
Current cluster layout version: 0
==== STAGED ROLE CHANGES ====
ID Tags Zone Capacity
1ea44a6a247a66b2 [] apne1-az1 1000.0 MB
==== NEW CLUSTER LAYOUT AFTER APPLYING CHANGES ====
ID Tags Zone Capacity Usable capacity
1ea44a6a247a66b2 [] apne1-az1 1000.0 MB 1000.0 MB (100.0%)
Zone redundancy: maximum
==== COMPUTATION OF A NEW PARTITION ASSIGNATION ====
Partitions are replicated 1 times on at least 1 distinct zones.
Optimal partition size: 3.9 MB
Usable capacity / total cluster capacity: 1000.0 MB / 1000.0 MB (100.0 %)
Effective capacity (replication factor 1): 1000.0 MB
apne1-az1 Tags Partitions Capacity Usable capacity
1ea44a6a247a66b2 [] 256 (256 new) 1000.0 MB 1000.0 MB (100.0%)
TOTAL 256 (256 unique) 1000.0 MB 1000.0 MB (100.0%)
To enact the staged role changes, type:
garage layout apply --version 1
You can also revert all proposed changes with: garage layout revert
変更を適用します。
docker exec -it garaged /garage layout apply --version 1

バケットの作成
バケット「laravel」を作成してみます。
docker exec -it garaged /garage bucket create laravel

バケットの一覧:
docker exec -it garaged /garage bucket list

バケットの詳細:
docker exec -it garaged /garage bucket info laravel

キーの作成
APIキーを作成して、バケットにアクセス権と共に割り当てをしないと、
バケットへのアクセスができません。
「laravel-app-key」という名前のAPIキーを作成してみます。
docker exec -it garaged /garage key create laravel-app-key

Secret key はメモって保存しておきましょう。
作成されたキーリスト:
docker exec -it garaged /garage key list
作成されたキーの詳細:
docker exec -it garaged /garage key info laravel-app-key

バケットへのキー割り当て
作成したAPIキー「laravel-app-key」をバケット「laravel」へ割り当て、
読み書き、オーナー権限を付与します。
docker exec -it garaged /garage bucket allow \
--read \
--write \
--owner \
laravel \
--key laravel-app-key

Website access: false ってのが気になりますね。
バケットへのWebsite accessの許可は次のコマンドでできるようです。
docker exec -it garaged /garage bucket website --allow laravel
aws cliでファイルのアップロードとダウンロードテスト
Pythonモジュールのaws cliをインストールします。
python -m pip install --user awscli

aws cli用の設定ファイル「~/.awsrc」を作成します。
export AWS_ACCESS_KEY_ID=GK0e87d8fabfeea62ac249e225
export AWS_SECRET_ACCESS_KEY=4cd36c5cea19a75043f68ada1246b4197cf6a5b3659ad4ad6679b1db57457741
export AWS_DEFAULT_REGION='garage'
export AWS_ENDPOINT_URL='http://localhost:3900'
aws --version
AWS_ACCESS_KEY_ID は「garage key list」で表示されるIDの値です。
AWS_SECRET_ACCESS_KEY がAPIキー作成時の「Secret key」です。

少なくとも awscli >=1.29.0 または >=2.13.0 が必要で、それ以外の場合は、
各 awscli 呼び出しで –endpoint-url を明示的に指定する必要があります。
設定を反映させます。
source ~/.awsrc

では、aws cliを使ってgarageにアクセスしてみましょう。
▼バケット一覧の取得
aws s3 ls

▼バケット「laravel」へファイル「greeting.txt」をアップロード
aws s3 cp greeting.txt s3://laravel/greeting.txt

▼バケット「laravel」内のオブジェクト一覧取得
aws s3 ls s3://laravel

▼バケット「laravel」内のオブジェクト「greeting.txt」を
ローカルの「greeting2.txt」としてダウンロード
aws s3 cp s3://laravel/greeting.txt greeting2.txt

awscli は、バケットの作成、リクエストの事前署名、Web サイトの管理など、より高度な操作に使用できることに注意してください。詳細については、完全な(s3)ドキュメントをお読みください。
ただし、ACL やポリシーなどの一部の機能は実装されていません。s3 互換性リストを確認してください。
docker-compose.yml を作ってみる
Garageのサービスコンテナと、GarageのWeb UIコンテナを構築します。
GarageにはWeb UIが付属していないので、サードパーティのパッケージを使います。
「docker run」コマンドでコンテナ起動する場合は、ログインユーザー情報をシングルクオートで括ることでログインユーザーの指定が可能ですが、docker-compose.yml の場合はエラーが出て設定できません。。
この件はIssueを登録しておきました。
パッケージの設計が悪いですね。
しかし、今回はローカルの開発環境用なので、認証はなくても構いません。(妥協)
2025/11/16追記:この問題への応急措置
docker compose は、$の直後にアルファベットが続くと変数名として解釈するようです。
正規表現パターンでいくと
\$[A-Z][A-Z0-9_]*
または
\$[A-Za-z][A-Za-z0-9_]*
このパターンに合致する場合は $ を $ でエスケープする必要があります。
$ABC ⇒ $$ABC
実際、次の設定で、「username」「password」でログインできるようになりました。
environment:
API_BASE_URL: "http://garaged:3903"
S3_ENDPOINT_URL: "http://garaged:3900"
AUTH_USER_PASS: "username:$2y$10$$BWKz2wI45JQOS6NBXNXY2.qq3vt/SEnJYaxSi4/XwZijF6ortWPKi"
しかし、こんな対応はしたくないもんですね。
堅牢ではあって且つより簡単な設定方法にして欲しいものです。
どのみち開発環境用なんだからMinIO同様にこんなんで良いと思うけど。
environment:
- MINIO_ROOT_USER=minio
- MINIO_ROOT_PASSWORD=minio123
※2025/11/18追記:Issueに反応ないし、アップデートもかなり散発的で前回が9月だし、修正は期待できなさそうです。
テストコードも見当たらないし、どっかの国のサーバーにアクセスなんて変なコードは見当たらなかったけど、こうなるとWeb UIは各自自作した方が良いのでは?と思ってしまいます。
Laravel + Vue Starter Kitでバイブコーディングすればすぐ出来るのでは?
▼「docker run」コマンドでコンテナ起動する例(ログイン情報指定)
docker run -d \
--name garage-web \
-p 3909:3909 \
-v ./docker/garage/garage.toml:/etc/garage.toml:ro \
-e API_BASE_URL="http://garaged:3903" \
-e S3_ENDPOINT_URL="http://garaged:3900" \
-e AUTH_USER_PASS='hoge:$2y$10$iv89.I0SGg3wr9uZZZaOmOXx3OSkTiyLcrD8f/a9tgfwbm3KELcJm' \
khairul169/garage-webui:latest
※ここで指定するログイン情報は「htpasswd」コマンドで生成してコピペします。
※「htpasswd」コマンドが無い場合はaptコマンドで「apache2-utils」をインストールします。
sudo apt install apache2-utils
※ログイン情報生成例
htpasswd -nbBC 10 hoge password
では、本題の docker-compose.yml を作ります。
▼docker-compose.yml
services:
garaged:
image: dxflrs/garage:v2.1.0
container_name: garaged
volumes:
- ./docker/garage/garage.toml:/etc/garage.toml
- ./docker/garage/meta:/var/lib/garage/meta
- ./docker/garage/data:/var/lib/garage/data
restart: unless-stopped
ports:
- 3900:3900
- 3901:3901
- 3902:3902
- 3903:3903
garage-webui:
image: khairul169/garage-webui:latest
container_name: garage-webui
restart: unless-stopped
volumes:
- ./docker/garage/garage.toml:/etc/garage.toml:ro
ports:
- 3909:3909
environment:
API_BASE_URL: "http://garaged:3903"
S3_ENDPOINT_URL: "http://garaged:3900"
コンテナを構築&起動します。
docker compose up -d

Garage Web UIを使ってみる
Webブラウザで http://localhost:3909/ にアクセスします。

では、クラスターレイアウトを作成していきます。
サイドメニューの「Cluster」をクリックします。
クラスターリストの右端にあるメニューボタンをクリックし「Assign」を選択します。

モーダルが表示されるので「Zone」にゾーン名を入力し「Create ・・・」を選択します。

「Capacity」の欄に容量の数字を入力し、「Save」ボタンを押下します。

クラスターレイアウトの変更がステージングされた状態(背景黄色)になるので
「Apply」ボタンを押下します。

結果が表示されます。

続いて、バケットを作成していきます。
サイドメニューの「Buckets」をクリックします。
画面右上の「+ Create Bucket」ボタンを押下します。

モーダルが表示されるので、バケット名を入力して「Submit」ボタンを押下します。

作成されたバケットが表示されます。

続いて、APIキーを作成してバケットに割り当てていきます。
サイドメニューの「Keys」をクリックします。
画面右上の「+ Create Key」ボタンを押下します。

モーダルが表示されるので、キーの名称を入力して「Submit」ボタンを押下します。

作成されたキーのリストが表示されるので、「View」ボタンを押下します。
「Secret Key」が表示されるので、右側のコピーアイコンをクリックしてコピーし、どこかに保存しておきましょう。
最後に、バケットへのAPIキーの割り当てをしていきます。
サイドメニューの「Buckets」をクリックします。
対象バケットの「Manage」ボタンを押下します。

Web Accessを許可する場合は「Website Access」を「Enabled」にしておきましょう。

下の方にサブドメイン形式のバケットのURLが表示されます。
次に、画面上の「Permissions」タブを開いて「+ Allow Key」ボタンを押下します。

モーダルで対象のキーのパーミッションをチェックして「Submit」ボタンを押下します。

これでバケットへのアクセス権限付与が完了しました。

では、Web UI経由でファイルのアップロードとダウンロードをしていきます。
画面上部の「Browse」タブを選択し、右端のアップロードボタンを押下します。

適当にファイルを選択してアップロードします。

対象ファイルの右側にあるダウンロードボタンを押下するとダウンロードできます。

Laravel tinker を使った動作テスト
Laravel tinker を使って動作テストをしていきます。
まずはLaravelプロジェクトを html フォルダ内に作成していきます。
今回は別にLaravelインストーラーは要らないでしょう。
Composerでプロジェクト作成していきます。
aws cli用の「~/.awsrc」の影響を排除するために、新しいターミナルを開いて実行します。
composer create-project laravel/laravel:^12 html

プロジェクトフォルダに入ります。
cd html
続いて、S3ドライバーの league/filesystem-aws-s3-v3 をインストールします。
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies

「.env」を編集し、次の内容を追記します。
AWS_ACCESS_KEY_ID=GK96c8b79c86d41d0067699b1f
AWS_SECRET_ACCESS_KEY=8a51dc44408d0f09687905ac31d203a83957534187ef722007e159ba3de818b3
AWS_DEFAULT_REGION=garage
AWS_BUCKET=laravel
AWS_ENDPOINT=http://localhost:3900
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_URL=http://laravel.web.garage.localhost:3902
AWS_ACCESS_KEY_ID はWeb UIのKey IDをコピペ。
AWS_SECRET_ACCESS_KEY もWeb UIのSecret Keyをコピペ。
tinkerを起動します。
php artisan tinker

まずはバケット内のオブジェクトリストを取得してみましょう。
use Illuminate\Support\Facades\Storage;
$files = Storage::disk('s3')->files('/');
$files;

次に、Laravelプロジェクトフォルダの README.md でもアップロードしてみましょう。
Storage::disk('s3')->put('README.md', file_get_contents('README.md'));

アップロードできたようです。
では、greeting.txtの内容を取得してみましょう。
Storage::disk('s3')->get('greeting.txt');

greeting.txtの内容が表示されました。
greeting.txtのURLを取得してみます。
$url = Storage::disk('s3')->url('greeting.txt');

取得したURLをWEBブラウザで開いてみます。

ファイルの内容が表示されました。
所感
隅々まで見たわけではないですが、使い勝手は悪くありません。
設定については、初回の設定(おまじないレベルの認識)以外まったく見ていません。(反省)
ただ、自分が使う範囲(ファイルのCRUD・URL公開)が出来ているのでこれで充分です。
パフォーマンスの計測はしていませんが、まあ、触った限り開発で困ることは無さそうです。

※2025/11/20追記
▼パフォーマンスの計測をしてみました。
Web UI はサードパーティなので、Garage本家の物ではないですが、
認証以外は困っていません。。Web UIの認証は何とかしてほしいな。
総評としては、MinIOの代替え(開発環境のS3スタブ用途)としては十分です。
以上です。
- 3
- 1
- 0
- 0





コメント