ローカル環境でAWS S3をモックさせるMinIOを使ってみよう

AWSのS3を利用するアプリケーション開発において、実際にクラウド上のS3とやり取りするのは何かと手間がかかりますよね。
そこで役立つのが、S3互換のオブジェクトストレージである MinIO です。
MinIOを使えば、ローカル環境でS3とほぼ同じ操作を試せるため、開発効率が格段に向上します。

この記事では、Go言語でS3を利用するアプリケーション開発者向けに、Dockerを使ってMinIOを起動し、基本的な操作を行う手順を解説します。

## 公式サイト
https://min.io/

## 前提条件
- macOS（筆者の動作環境 macOS Sequoia 15.4）
- Docker Desktop for Mac がインストール済み
- Go 1.18 以上

## ファイル構成
```
├ docker
│ └ docker-compose.yml
│
├ download
├ upload
│ └ sample.txt  // MinIOへのアップロードサンプルファイル
├ go.mod
├ go.sum
└ main.go
```


## docker-compose設定

```docker:docker-compose.yml
version: "3.8"
services:
  minio:
    image: minio/minio:latest
    container_name: minio
    ports:
      - "9000:9000"  # S3 API
      - "9001:9001"  # MinIO Console
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
volumes:
  minio-data:
```

## サーバ起動
以下コマンドを叩いてdockerのコンテナを起動します。
```
$ cd docker

$ docker-compose up -d
```


起動確認
```
$ docker ps
CONTAINER ID   IMAGE                COMMAND                   CREATED         STATUS         PORTS                              NAMES
4b6330ecda35   minio/minio:latest   "/usr/bin/docker-ent…"   4 seconds ago   Up 4 seconds   0.0.0.0:9000-9001->9000-9001/tcp   minio
```

起動したコンテナのログも見てみましょう。
```
$ docker logs 4b6330ecda35

MinIO Object Storage Server
Copyright: 2015-2025 MinIO, Inc.
License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html
Version: RELEASE.2025-04-22T22-12-26Z (go1.24.2 linux/arm64)

API: http://172.18.0.2:9000  http://127.0.0.1:9000 
WebUI: http://172.18.0.2:9001 http://127.0.0.1:9001  

Docs: https://docs.min.io
WARN: Detected default credentials 'minioadmin:minioadmin', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables
```

warningとして以下が表示されています。
```
WARN: Detected default credentials 'minioadmin:minioadmin', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables
```

これはデフォルトのアクセスキーとシークレットキーを設定されているためアラートが表示されているものとなるので必要に応じて変更を行ってください。
今回はローカルでテスト利用のためそのまま続行とします。

## MinIOの管理画面確認

ログを確認すると以下が表示されていますね。
```
WebUI: http://172.18.0.2:9001 http://127.0.0.1:9001
```

こちらはMinIOの管理画面にアクセスするためのURLが表示されているものとなるので、
ブラウザに`http://127.0.0.1:9001`を打ち込んでみましょう。

![スクリーンショット 2025-05-07 10.12.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076094/9e649969-47d9-47e1-a7dd-19d835dc4123.png)

MinIOの管理画面が表示されました。
次に「Username」「Password」にdocker-compose.ymlにて設定した`MINIO_ROOT_USER` `MINIO_ROOT_PASSWORD`の値を入力してログインしてみましょう。
今回はどちらも`minioadmin`になります。

![スクリーンショット 2025-05-07 10.13.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076094/3e3ed193-1f75-4874-86d4-8056dbbc55a9.png)

管理画面にログインできました。
今回のサンプルコードで作成したバケットやアップロードしたファイルはこちらからも確認することが可能です。

MinIOの接続確認ができたので次はサンプルコードで試していきましょう。

## AWS SDK for Go v2 のインストール
S3互換APIを叩くためにAWS SDKを追加します。

```bash
$ go get github.com/aws/aws-sdk-go-v2
$ go get github.com/aws/aws-sdk-go-v2/config
$ go get github.com/aws/aws-sdk-go-v2/service/s3
```

※go.modファイルの作成がまだでしたら以下コマンドをサンプルに作成してください。
```bash
$ go mod init example.com
```


## サンプルコード

```go:main.go
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

const (
	endpoint        = "http://localhost:9000"
	accessKeyID     = "minioadmin"
	secretAccessKey = "minioadmin"
	bucketName      = "test-bucket"
	fileName        = "sample.txt"
	region          = "ap-northeast-1"
)

func createS3Client() (*s3.Client, error) {
	// 明示的にカスタムエンドポイントを指定
	customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
		return aws.Endpoint{
			URL:           endpoint,
			SigningRegion: region,
		}, nil
	})

	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithEndpointResolver(customResolver),
		config.WithCredentialsProvider(
			credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
		),
	)
	if err != nil {
		return nil, err
	}

	return s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.UsePathStyle = true // MinIOではPath-styleが必要
	}), nil
}

func main() {
	client, _ := createS3Client()
	ctx := context.TODO()

	bucket := bucketName
	// 1. バケット作成 （既存ならスキップ）
	_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
		Bucket: &bucket,
	})
	if err != nil {
		if strings.Contains(err.Error(), "BucketAlreadyOwnedByYou") {
			fmt.Println("ℹ️  既にバケットが存在しています:", bucket)
		} else {
			log.Fatalf("バケット作成失敗: %v", err)
		}
	} else {
		fmt.Println("✅ バケット作成:", bucket)
	}

	// 2. ファイルアップロード
	file, err := os.Open("./upload/" + fileName)
	if err != nil {
		log.Fatalf("ファイルオープン失敗: %v", err)
	}
	defer file.Close()

	_, err = client.PutObject(ctx, &s3.PutObjectInput{
		Bucket: &bucket,
		Key:    aws.String(fileName),
		Body:   file,
	})
	if err != nil {
		log.Fatalf("アップロード失敗: %v", err)
	}
	fmt.Println("✅ sample.txt をアップロードしました")

	// 3. ファイルダウンロード
	out, err := client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: &bucket,
		Key:    aws.String(fileName),
	})
	if err != nil {
		log.Fatalf("ダウンロード失敗: %v", err)
	}
	defer out.Body.Close()

	local, _ := os.Create("./download/" + "download.txt")
	defer local.Close()
	io.Copy(local, out.Body)
	fmt.Println("✅ download.txt を作成しました")
}

```

uploadディレクトリにMinIOにアップロードするサンプルテキストを作成します。
```
$ touch ./upload/sample.txt
```

作成されたファイルを開き任意の文字列を書き込みます。
```text:sample.txt
Hello MinIO!
```

## コマンド実行
main.goのファイルがあるディレクトリにて以下コマンドで実行してみましょう。
```bash
$ go run main.go
```

実行結果
```
✅ バケット作成: test-bucket
✅ sample.txt をアップロードしました
✅ download.txt を作成しました
```

無事に実行が完了しバケットの作成、及びファイルのアップロード、ダウンロードができているか確認してみましょう。

### アップロード確認
管理画面をブラウザにて確認することでバケットの作成及び、ファイルのアップロードができていることが確認できます。

バケット作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076094/70ba5706-47c6-4003-a6c5-c75015a555db.png)

ファイルアップロード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076094/f0e37e5a-76b2-4d19-87f5-f0092ac2f7db.png)


### ダウンロード確認
downloadファイル配下にダウンロードされたファイルが格納されていることがわかります。

```
$ ls download/
download.txt

$ cat download/download.txt 
Hello MinIO!
```

### コンテナ停止・削除
開発が終わったら、downコマンドでMinIOコンテナを停止しておきましょう。
```bash
$ cd docker 

$ docker compose down
```

### まとめ
MinIOは簡単にローカルでS3モック環境を構築でき、
AWS SDK for Go v2 と組み合わせることで、既存のS3コードをそのまま流用可能です。
テストやローカル開発時の依存を減らし、CIにも組み込みやすいものとなります。
MinIOでS3をモックして自由にテストを行いましょう。

以上、参考になれば幸いです。
ありがとうございました。

### 参考資料
[MinIO公式サイト](https://min.io/)
[MinIO Docker Quickstart](https://min.io/docs/minio/container/index.html)
[ローカルS3環境(minio)を構築する](https://qiita.com/reflet/items/3e0f07bc9d64314515c1)