そういう場面で使えるのが、S3互換APIを実装したオブジェクトストレージ「MinIO」です。自分たちが管理するLinuxサーバーにMinIOを立ててS3互換バックエンドとして指定すれば、AWSなしでtfstateの共有基盤が成立します。
この記事では、MinIOをTerraform backendに接続するための設定に絞って解説します。「どのポートで立てるか」「force_path_styleとは何か」「アクセスキーの必要最小権限はどう書くか」という実装の観点で説明します。Rocky Linux / RHEL9での動作を前提にしています。
この記事のポイント
・MinIOのS3互換バックエンドにはforce_path_styleとskip系3オプションの設定が必須
・regionはMinIOが使わない値でも us-east-1 のようなダミー文字列の指定が必要
・最小権限は GetObject・PutObject・DeleteObject・ListBucket・GetBucketVersioning の5権限
・Terraform 1.10以降は use_lockfile=true でDynamoDB不要のstateロックが使える
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
MinIOがS3互換バックエンドとして使える理由
TerraformのS3バックエンドは、厳密には「AWSのS3」ではなく「S3互換API」に対応しています。エンドポイントURLを差し替えるだけで、S3互換のオブジェクトストレージなら何でも接続先として使えるよう設計されています。MinIOはS3 APIをほぼ完全に実装しており、tfstateの読み書き(GetObject・PutObject)とstateロック(PutObject・DeleteObject)に必要なAPIもカバーしています。ただし、いくつかのAWS固有の検証処理が邪魔をするため、それをスキップする設定が必要です。この記事ではその設定を一つひとつ説明します。
MinIOをLinuxサーバーへインストールしてstateバケットを用意する
1. バイナリのダウンロードとsystemdサービス登録
MinIOはRPMパッケージとシングルバイナリの両方で配布されています。ここではバイナリを直接インストールする方法を使います。Rocky Linux 9 / RHEL9で確認済みの手順です。# MinIOバイナリをダウンロードして実行権限を付与 wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio mv minio /usr/local/bin/ # データ保管用ディレクトリとサービスユーザーを作成 useradd -r -s /sbin/nologin minio-user mkdir -p /data/minio chown minio-user:minio-user /data/minio
# /etc/default/minio を作成 cat << 'EOF' > /etc/default/minio MINIO_VOLUMES="/data/minio" MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=ChangeMeToStrongPassword EOF
# /etc/systemd/system/minio.service を作成 cat << 'EOF' > /etc/systemd/system/minio.service [Unit] Description=MinIO Object Storage After=network-online.target [Service] User=minio-user Group=minio-user EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES --address :9000 --console-address :9090 Restart=always LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now minio systemctl status minio
* minio.service - MinIO Object Storage Loaded: loaded (/etc/systemd/system/minio.service; enabled; preset: disabled) Active: active (running) since Thu 2026-09-18 09:12:34 JST; 5s ago Main PID: 3841 (minio) Memory: 138.2M CPU: 1.224s CGroup: /system.slice/minio.service └─3841 /usr/local/bin/minio server /data/minio --address :9000 --console-address :9090
2. バケットの作成とポート疎通確認
MinIOが起動したら、Terraformがstateを格納するバケットを作ります。バケット作成にはMinIO Clientの「mc」を使います(次のセクションで詳しく説明します)。まずMinIOが9000番ポートで待ち受けているか確認してください。Linux ポート確認の全コマンドも参考にしてください。# ssコマンドでMinIOが9000番ポートを待ち受けているか確認 ss -tlnp | grep 9000 # 出力例 LISTEN 0 4096 0.0.0.0:9000 0.0.0.0:* users:(("minio",pid=3841,fd=12))
backendブロックにS3互換オプションを設定する
1. backend設定の最小構成
Terraformの設定ファイルにbackendブロックを追加します。MinIOに接続するために必要な最小構成は次のとおりです。# backend.tf(またはmain.tfのterraformブロック内) terraform { backend "s3" { bucket = "tfstate" key = "prod/terraform.tfstate" region = "us-east-1" endpoint = "http://192.168.0.10:9000" access_key = "tf-access-key" secret_key = "tf-secret-key" force_path_style = true skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true } }
2. force_path_styleが必要な理由
S3のURLには「バーチャルホスト型」と「パス型」の2種類があります。AWSのデフォルトはバーチャルホスト型(https://bucket-name.s3.amazonaws.com/)ですが、MinIOはパス型(http://host:9000/bucket-name/)でリクエストを受け付けます。force_path_style = true を指定しないと、TerraformはMinIOに対してバーチャルホスト型のURLでリクエストを送ってしまい、名前解決に失敗します。Terraform 1.6以降では use_path_style = true という名前のオプションも追加されましたが、force_path_style は後方互換のため現在も機能します。どちらか一方を指定すれば問題ありません。3. skip_*系3オプションの意味
・skip_credentials_validation:AWSのSTS(Security Token Service)を呼んでアクセスキーが正規のものかチェックする処理をスキップします。MinIOにはSTSがないためtrueが必要です。・skip_metadata_api_check:EC2のインスタンスメタデータAPI(169.254.169.254)を呼んでIAMロールを取得しようとする処理をスキップします。MinIOサーバーはEC2ではないためtrueが必要です。
・skip_region_validation:regionの値がAWSの実在するリージョン名かどうかの検証をスキップします。MinIOはregionを使いませんが、backendブロックの仕様上regionを省略できないため、ダミー値でよいようにするためtrueが必要です。
4. terraform initで初期化する
terraform init # 成功時の出力例 Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... Terraform has been successfully initialized!
TerraformのIaC設計をより体系的に学びたい方は Terraform実践コース(terraform.linuxmaster.jp) も参考にしてください。
最小権限のアクセスキーを発行する
rootアカウントのアクセスキーをTerraformに直接使いまわすのは避けてください。MinIO Clientの「mc」を使ってTerraform専用ユーザーを作り、必要な操作だけを許可したアクセスキーを払い出します。1. mcクライアントのセットアップ
# mcバイナリをダウンロード wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc mv mc /usr/local/bin/mc # MinIOサーバーのエイリアスを登録(rootアカウントで認証) mc alias set tfminio http://192.168.0.10:9000 minioadmin ChangeMeToStrongPassword # 接続確認 mc admin info tfminio # 出力例 * 192.168.0.10:9000 Uptime: 12 minutes Version: 2024-09-13T20:41:23Z Network: 1/1 OK Drives: 1/1 OK Pool: 1
2. stateバケットの作成とTerraform専用ユーザーの追加
# stateバケットを作成 mc mb tfminio/tfstate # Terraform専用ユーザーを作成 mc admin user add tfminio tfuser TfUserPassword123 # 最小権限ポリシーのJSONを作成 cat << 'EOF' > /tmp/tfstate-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::tfstate/*" }, { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketVersioning" ], "Resource": "arn:aws:s3:::tfstate" } ] } EOF # ポリシーを登録してユーザーに適用 mc admin policy create tfminio tfstate-rw /tmp/tfstate-policy.json mc admin policy attach tfminio tfstate-rw --user tfuser
・GetObject:tfstateファイルを読み取る(terraform plan / apply時の現在状態確認)
・PutObject:tfstateファイルを書き込む(apply後の状態更新とstateロックファイル作成)
・DeleteObject:stateロックファイルを削除する(ロック解放)
・ListBucket:バケット内のオブジェクト一覧を取得する(terraform init時のstate確認)
・GetBucketVersioning:バケットのバージョニング設定を確認する(backend初期化時に参照)
3. サービスアカウント(アクセスキーペア)を払い出す
MinIOではユーザーのログインパスワードとは別に、アクセスキーIDとシークレットキーのペアを「サービスアカウント」として発行できます。Terraformが使うのはこのサービスアカウントです。# tfuserのサービスアカウントを作成(鍵ペアが自動生成される) mc admin user svcacct add tfminio tfuser --name terraform-ci # 出力例(この時点しか確認できないので必ず控えること) Access Key: AKIAIOSFODNN7TFUSER1 Secret Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1
access_key と secret_key に設定します。Secret Keyはこのタイミングしか確認できないため、安全な場所に保存してください。Terraform 1.10以降はuse_lockfileでDynamoDB不要のstateロックを使う
Terraform 1.10より前のバージョンでは、S3バックエンドのstateロックにDynamoDBが必要でした。MinIOはDynamoDBを実装していないため、古いバージョンではdynamodb_table を指定せずstateロックなしで運用するしかありませんでした。Terraform 1.10で追加された
use_lockfile = true オプションを使うと、DynamoDBの代わりにS3バケット内に .tfstate.lock ファイルを置くことでstateロックを実現できます。MinIOは通常のPutObject・DeleteObjectで動くため、この方式はMinIOでも正常に動作します。# Terraform >= 1.10 の場合:DynamoDB不要のstateロック設定 terraform { backend "s3" { bucket = "tfstate" key = "prod/terraform.tfstate" region = "us-east-1" endpoint = "http://192.168.0.10:9000" access_key = "AKIAIOSFODNN7TFUSER1" secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1" force_path_style = true skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true use_lockfile = true } }
use_lockfile を有効にする場合、ポリシーの s3:PutObject と s3:DeleteObject の権限がロックファイルの作成・削除に使われます。前の手順で設定したポリシーのままで問題なく動作します。トラブルシュート
「SignatureDoesNotMatch」が出る場合
access_keyまたはsecret_keyの値が間違っている可能性が高いです。コピー時に余分なスペースや改行が混入することがあります。mcコマンドで同じアクセスキーを使って接続確認すると切り分けやすくなります。# サービスアカウントのエイリアスを別名で作って動作確認 mc alias set tftest http://192.168.0.10:9000 AKIAIOSFODNN7TFUSER1 wJalrXUtnFEMI/K7MDENG/bPxRfiCYTFUSERKEY1 mc ls tftest/tfstate # 正常なら tfstate バケットの中身が表示される(空バケットなら空行)
「NoSuchBucket」が出る場合
mc mb でバケットを作成する前にterraform initを実行した場合に発生します。backend.tfの bucket で指定した名前と mc mb で作成したバケット名が一致しているか確認してください。バケット名は英小文字・数字・ハイフンのみ使用できます(S3と同じルール)。stateロックが残留した場合
terraform apply が途中で強制終了するなどしてstateロックが残ることがあります。terraform force-unlock でロックIDを指定して解除できます。# ロックIDはエラーメッセージの中に含まれる # Error acquiring the state lock: # Lock ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
use_lockfile = true を使っている場合、ロックファイルはバケット内の <key>.lock オブジェクトとして存在します。terraform force-unlock で解除できない場合は mc rm で直接削除できますが、本当に誰もapplyを実行中でないことを確認してから行ってください。本記事のまとめ
| 設定項目 | 値・指定内容 | 理由 |
|---|---|---|
| endpoint | http://MinIOサーバーIP:9000 |
MinIOのAPIエンドポイントを指定 |
| region | us-east-1(ダミー値でOK) |
MinIOは使わないが省略不可 |
| force_path_style | true |
MinIOはパス型URLのみ対応 |
| skip_credentials_validation | true |
AWS STS呼び出しをスキップ |
| skip_metadata_api_check | true |
EC2メタデータAPIをスキップ |
| skip_region_validation | true |
AWS実在リージョン検証をスキップ |
| use_lockfile(1.10以降) | true |
DynamoDB不要のファイルベースstateロック |
| 必要な権限(5つ) | GetObject / PutObject / DeleteObject / ListBucket / GetBucketVersioning |
tfstateの読み書きとロック操作 |
force_path_style と skip_* 系3オプションがセットになって初めて正しく接続できます。アクセスキーはmcコマンドで専用ユーザーにサービスアカウントとして払い出し、必要な5権限に絞るのがLinuxサーバー上での自前運用の基本です。TerraformのIaC設計・運用をステップごとに学ぶなら Terraform実践コース(terraform.linuxmaster.jp) もご覧ください。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformでDynamoDBテーブルを宣言的に定義する設計|GSI・TTL・ストリームの設定変更を安全に差分適用する
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます