AWSを触るのが怖いので、Terraformで「作って壊す」を体験してみた

AWSを学びたいものの、料金やリソースの消し忘れが怖く、なかなか手を動かせずにいた。そこで今回はTerraformを使い、空のS3バケットを作成して削除するまでを試した。

TL;DR:この記事でやること

要するに、Terraformを使って東京リージョンに空のS3バケットを1つ作る。AWS上に作成できたことを確認したら、terraform destroyで削除する。

今回はTerraformを体系的に学び切ろうとはしない。plan → apply → 確認 → destroyという「作って壊す」流れを一周することが目標である。

ローカルPCからTerraformでAWSのS3バケットを作成する構成

なぜTerraformを使うのか

Terraformは、IaC(Infrastructure as Code)と呼ばれる考え方を実現するためのツールの1つである。サーバーやストレージなどの構成を、管理画面の操作ではなくコードで定義する。

AWSコンソールでリソースを作ると、どの項目を設定したのか後から思い出せないことがある。Terraformなら、作りたい構成をmain.tfへ残し、実行前にterraform planで変更内容を確認できる。

さらに、今回作ったリソースはterraform destroyで削除できる。「何を作るのか」「実行すると何が変わるのか」「最後に何を消すのか」を確認しながら進められる点が、AWSを触るのが怖い初心者にも向いていると考えた。

事前準備

  • Terraformのインストール: Homebrewを使用
  • Terraformのバージョン: v1.16.0(darwin_arm64)
  • AWS CLI: v2.36.34(Python 3.14.7、Darwin 25.6.0、arm64)
  • AWS認証の状態: aws login --region ap-northeast-1によるブラウザ認証に成功
  • 使用リージョン: アジアパシフィック(東京)ap-northeast-1

AWSの認証情報はTerraform設定へ直接書かない。AWSアカウントIDやARNがターミナルやAWSコンソールに表示された場合は、スクリーンショットを公開する前にマスクする。

Terraformをインストールする

今回はApple Silicon搭載MacでHomebrewを使用した。

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform version

brew tapは、HashiCorp公式のHomebrewリポジトリを追加する操作である。続くbrew installでTerraform本体をインストールする。

最後にterraform versionを実行し、Terraform v1.16.0がdarwin_arm64向けにインストールされたことを確認した。コマンドはHashiCorp公式のインストール手順を参照した。

Terraformのインストールとバージョン確認

AWS CLIをインストールする

TerraformがAWSを操作できるようにする準備として、AWS CLIもHomebrewでインストールした。

brew install awscli
aws --version

今回の環境では、依存パッケージの一覧とともにインストールを続行するか確認され、yを入力した。環境によってはこの確認が表示されない場合がある。

インストール後、AWS CLI v2.36.34がarm64向けに導入されたことを確認した。

AWSへログインする

AWS CLIをインストールしただけでは、どのAWSアカウントを操作するのか決まっていない。

今回は長期Access Keyを保存せず、AWS公式ドキュメントで案内されているaws loginによるブラウザ認証を使用した。

aws login --region ap-northeast-1

ブラウザで認証した後、AWS CLIから現在の認証状態を確認した。

aws sts get-caller-identity

このコマンドの結果にはAWSアカウントIDやARNが含まれる。スクリーンショットや記事へ載せる場合は、必ず該当箇所をマスクする。

なお、今回の環境ではaws loginにリージョンを指定しても、AWS CLIの既定リージョンは設定されなかった。後の確認作業で次のコマンドを実行した。

aws configure set region ap-northeast-1

今回のTerraform設定

今回作るものは、東京リージョンap-northeast-1に置く空のS3バケット1個である。画像や文書などのオブジェクトはアップロードせず、Webサイト公開、バージョニング、Object Lockなども設定しない。

TerraformでS3を作成して削除するまでの流れ

main.tfやTerraformのstateは自分のMac側にあり、実物のS3バケットはAWS側に作られる。Terraformは両者の間に入り、設定を読み取ってAWSへ作成・削除を指示し、その結果をstateへ記録する仲介役である。

バケット名はterraform-practice-から始まり、重複を避けるため末尾をTerraformに自動生成してもらう。学習用だと分かるタグも1つ付ける。

実験後のディレクトリ構成は次のようになった。

ディレクトリ構成

terraform-s3/
├── .gitignore
├── .terraform/
├── .terraform.lock.hcl
├── main.tf
├── terraform.tfstate
└── terraform.tfstate.backup

自分で用意したのはmain.tf.gitignoreである。.terraform.lock.hclは選択されたProviderのバージョンを再現するためGitで管理する。一方、.terraform/terraform.tfstateterraform.tfstate.backupはTerraformがローカルに生成する。stateには構成によって機密情報が含まれる可能性があるため、Gitへ追加しない。

実際に使用したmain.tfは次のとおりである。認証情報やAWSアカウントIDは記載していない。

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "practice" {
  bucket_prefix = "terraform-practice-"

  tags = {
    Purpose = "terraform-practice"
  }
}

output "bucket_name" {
  description = "Terraformで作成したS3バケット名"
  value       = aws_s3_bucket.practice.bucket
}

各ブロックの役割は次のとおりである。

  • required_providers: この設定でHashiCorp公式のAWS Providerを使用する。~> 6.0は6系のバージョンを使用する指定である。今回のterraform initではv6.62.0が選ばれた。
  • provider "aws": TerraformがAWSを操作するときの共通設定である。今回は東京リージョンを指定した。
  • resource "aws_s3_bucket" "practice": 実際に作るS3バケットを表す。practiceはTerraform設定内で参照するための名前であり、AWS上のバケット名ではない。
  • bucket_prefix: バケット名の先頭だけを指定し、末尾を自動生成する。S3バケット名の重複を避けやすくなる。
  • tags: AWSコンソール上でリソースの目的を識別するための情報である。
  • output "bucket_name": apply後に確定した実際のバケット名をターミナルへ表示する。新しいAWSリソースを作る設定ではない。

この設定ではforce_destroyを指定していないため、Providerの既定値はfalseになる。バケット内にオブジェクトが存在する場合、それらをTerraformが勝手に消してバケットを削除する動作にはならない。

また、新規S3バケットはAWSの既定設定でパブリックアクセスがブロックされる。ただし、今回のmain.tfではBlock Public AccessをTerraformのリソースとして明示的には管理していない。AWS公式ドキュメントでも、パブリックアクセスを許可する明確な理由がなければブロックを維持することが推奨されている。

terraform init

terraform initは、設定で必要になるProviderなどを準備するコマンドである。

初期化に成功し、HashiCorp公式のAWS Provider v6.62.0がインストールされた。Providerの選択バージョンを記録する.terraform.lock.hclも生成された。

この時点ではAWSリソースはまだ作成されていない。

initはS3を作るコマンドではなく、このTerraformプロジェクトを実行できる状態に準備するコマンドである。

terraform initの成功画面

terraform validate

terraform validateで、Terraform設定の文法と構成が有効かを確認した。

Success! The configuration is valid.

このコマンドもAWSリソースを作らない。planへ進む前に、設定ファイルの基本的な誤りを見つけるための確認工程である。

terraform plan

terraform planは、実際にAWSへ変更を加える前に、何が作成・変更・削除される予定かを確認するコマンドである。

確認したポイント:

  • 作成対象はaws_s3_bucket.practiceの1件だけだった。
  • Plan: 1 to add, 0 to change, 0 to destroy.と表示された。
  • 予想外の変更や削除はなかった。

出力中のknown after applyは、バケット名などの値がAWSで作成された後に確定することを表している。planの時点で値が見えないこと自体はエラーではない。

terraform planのサマリー

terraform apply

terraform applyで、確認した構成をAWSへ反映する。

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.と表示され、S3バケット1個の作成に成功した。

バケットの作成自体は約1秒、コマンド全体は約31秒だった。

承認画面では、作成1件・変更0件・削除0件であることを再確認してからyesを入力した。作成後、outputに実際のバケット名が表示された。

AWS上でS3バケットを確認

AWSコンソールまたはAWS CLIで、S3バケットが作成されたことを確認する。

AWSコンソールの汎用バケット一覧で、terraform-practice-から始まるバケットが1件存在し、AWSリージョンがアジアパシフィック(東京)ap-northeast-1であることを確認した。AWS CLIのget-bucket-locationでもLocationConstraint: ap-northeast-1が返り、同じ結果を確認できた。

Terraform側でもterraform state listを実行し、管理対象としてaws_s3_bucket.practiceだけが記録されていることを確認した。

AWSコンソールで東京リージョンのS3バケットを確認

terraform destroy

terraform destroyの実行前に、削除対象が今回作成したリソースだけであることを確認する。承認後、削除完了をAWS側でも確認する。

terraform destroy実行前の確認

実行前にPlan: 0 to add, 0 to change, 1 to destroy.と、削除対象がaws_s3_bucket.practiceの1件だけであることを確認した。

yesで承認後、Destroy complete! Resources: 1 destroyed.と表示され、Terraformによる削除に成功した。バケットの削除自体は0秒、コマンド全体は約1分38秒だった。

削除後にterraform state listを再実行すると何も表示されず、Terraformの管理対象が0件になったことも確認できた。

terraform destroyの完了画面

destroy後にAWSコンソールを更新するとS3の開始画面へ戻り、作成したバケットが一覧に残っていないことを確認した。

さらに、AWS CLIでバケット一覧を名前の接頭辞で絞り込んだ。

aws s3api list-buckets \
  --query "Buckets[?starts_with(Name, 'terraform-practice-')].Name"

結果が空の配列になり、terraform-practice-から始まるバケットがAWS側に残っていないことを確認できた。Terraformのstateが空であることと、AWS上に実物が残っていないことは別々に確認すると安心である。Terraformのstateに存在しないAWSリソースは、terraform destroyの管理対象にならないためである。

destroy後のAWSコンソール

ハマったこと

AWS CLIをインストールしただけではAWSを操作できなかった

  • エラー: NoCredentials: Unable to locate credentials
  • 最初の予想: AWS CLIのインストールに失敗している可能性を考えた。
  • 原因: AWS CLI本体は動作していたが、AWSアカウントへ接続するための認証情報がまだ設定されていなかった。
  • 解決方法: aws login --region ap-northeast-1を実行し、ブラウザで認証した。AWSから認証情報の共有成功が表示された。
  • 初心者向けに言い換えると: AWS CLIという道具は用意できたが、誰としてAWSを操作するのかをまだAWSへ伝えていない状態だった。

AWS CLIでリージョン未指定エラーになった

  • エラー: NoRegion: You must specify a region.
  • 最初の予想: IAM権限に問題がある可能性を考えた。
  • 原因: IAM権限ではなく、AWS CLIで使用する既定リージョンが未設定だった。
  • 解決方法: aws configure set region ap-northeast-1で既定リージョンを東京に設定した。aws configure get regionget-bucket-locationを再実行し、ap-northeast-1を確認できた。
  • 初心者向けに言い換えると: Terraformには東京リージョンを指定していたが、その設定はAWS CLIには自動共有されない。TerraformとAWS CLIはそれぞれリージョン設定を持つ。
リージョン未設定エラーの解決とterraform stateの確認

実際にかかった費用

実験直後にAWSの請求画面を確認したところ、2026年8月のS3コストは$0.00と表示されていた。今回は空のバケットを短時間だけ作成し、オブジェクトは保存していない。

ただし、これを「費用が完全に0だった」と断定することはできない。Amazon S3では保存容量だけでなく、PUT、LIST、GETなどのリクエストにも料金が設定されている。Amazon S3の公式料金ページによると、AWSコンソールでの閲覧もGETやLISTなどのリクエストとして扱われ、DELETEリクエストは無料である。

また、AWSの請求データには反映まで最大約24時間かかる場合があり、表示時の丸めによって微小な料金が$0.00に見える可能性もある。そのため、本記事では「確認時点の請求画面ではS3が$0.00だった」とする。AWS Billingの公式ドキュメント

やってみて理解したこと

  • Terraform CLIからAWS Providerを通してS3バケットを作成できる。AWS CLIは、作成後のリージョン確認や削除後の検索に利用できる。
  • bucket_prefixを使うと、意味のある接頭辞を残しつつ、重複しにくいバケット名をTerraformに生成してもらえる。
  • S3バケット名はAWSパーティション内で一意であり、URLにも現れる公開識別子である。秘密情報を名前に含めず、記事では不要な部分を伏せる。
  • terraform state listが空であることと、AWS上にリソースが残っていないことは別々に確認する。両方を確認すると、消し忘れへの不安を減らせる。

次に試すこと

  • S3の設定を1つ追加し、terraform planの差分を読む
  • EC2を作る前に料金と削除対象を確認する
  • S3 + CloudFrontの構成へ進む

まとめ

Terraformを使い、東京リージョンに空のS3バケットを1個作成し、AWSコンソールとCLIで確認した後、terraform destroyで削除できた。planで変更内容を事前確認し、削除後はTerraformのstateとAWS上の実物を別々に確認することで、「何を作り、何を消したのか」を追いやすかった。

一方、AWS CLIの認証とリージョン設定はTerraformとは別に必要であり、実際に手を動かして初めて違いを理解できた。小さなS3バケットから「作って壊す」サイクルを一度通したことで、次のAWSリソースも同じ確認手順で試せそうだ。