- 1 Terraform 基礎 — IaC 入門ハンズオン:S3・EC2をコードで構築するゼロからの実践ガイド
- 1.1 目次
- 1.2 Section 1: 概念編 — IaC・Terraform アーキテクチャ・HCL 基本構文
- 1.3 Section 2: アーキテクチャ解説
- 1.4 Section 3: ハンズオン — ゼロからTerraformで AWS 環境を構築する
- 1.5 Section 4: 実践Tips — チームでTerraformを使いこなす
- 1.6 Section 5: ハンズオン後の削除手順
- 1.7 Section 6: まとめ
Terraform 基礎 — IaC 入門ハンズオン:S3・EC2をコードで構築するゼロからの実践ガイド
公開日: 2026-04-13 / 難易度: 初級〜中級 / 所要時間: 約120分
この記事で学ぶこと
– IaC(Infrastructure as Code)と Terraform の基本概念・位置づけ
– HCL(HashiCorp Configuration Language)の基本構文(resource / variable / output など)
– S3 バケット・EC2 インスタンスを Terraform でコード化する手順(Step A〜B)
– 変数化(variables.tf/terraform.tfvars)と出力値(outputs.tf)の活用(Step C〜D)
– S3 + DynamoDB を使ったリモート State 管理の実装(Step E)前提条件
– AWSアカウント(無料利用枠可)
– IAMユーザー(AdministratorAccess)とアクセスキーの作成
– Gitの基礎知識(git clone/git commit程度)
目次
- Section 1: 概念編 — IaC・Terraform アーキテクチャ・HCL 基本構文
- 1-1. IaC(Infrastructure as Code)とは
- 1-2. Terraform の位置づけ
- 1-3. Terraform のアーキテクチャ
- 1-4. HCL 基本構文
- 1-5. Terraform のバージョン管理
- Section 2: アーキテクチャ解説
- 2-1. Terraform ワークフロー全体図
- 2-2. State 管理の仕組み
- 2-3. Provider と Resource の関係
- Section 3: ハンズオン(Step A〜E)
- 3-1. 環境準備
- 3-2. Step A: Hello Terraform — S3 バケット作成
- 3-3. Step B: EC2 インスタンス作成
- 3-4. Step C: 変数化
- 3-5. Step D: Output
- 3-6. Step E: リモート State 設定
- Section 4: 実践Tips
- 4-1. .gitignore の設定
- 4-2. 日常ワークフロー
- 4-3. terraform.lock.hcl の役割
- 4-4. モジュール化の基礎概念
- 4-5. CI/CD との統合
- Section 5: 削除手順
- Section 6: まとめ
Section 1: 概念編 — IaC・Terraform アーキテクチャ・HCL 基本構文
1-1. IaC(Infrastructure as Code)とは
IaC(Infrastructure as Code) とは、サーバー・ネットワーク・データベースといったインフラ構成を、手作業のコンソール操作ではなく コード(設定ファイル)として記述・管理 するアプローチです。
手動構築 vs コード管理
| 観点 | 手動構築(コンソール操作) | IaC(コード管理) |
|---|---|---|
| 再現性 | 担当者によって手順がブレる | 同じコードから何度でも同一環境を再現できる |
| バージョン管理 | 変更履歴が残らない | Git で差分・履歴を追跡できる |
| チーム開発 | 「誰が何を変えたか」不明 | PR レビューで変更を可視化・承認できる |
| スケール | 環境が増えると作業が線形に増大 | コードを再利用して複数環境に一括適用 |
| ドリフト検知 | 手動比較が必要 | terraform plan で差分を自動検出 |
| 障害復旧 | 手順書ベースで時間がかかる | コードを apply するだけで環境を再構築 |
| 学習コスト | 操作に慣れれば低い | HCL / DSL の学習が必要 |
| デバッグ | GUIで直感的に確認できる | エラーメッセージの読み方を覚える必要がある |
IaC がもたらす主なメリット
バージョン管理との統合
インフラの変更を git commit として記録できます。「いつ・誰が・なぜ変更したか」が一目でわかるため、障害発生時の原因特定が格段に速くなります。
git log --oneline
a3b1c2d feat: RDS インスタンスタイプを db.t3.medium に変更
b4c2d3e fix: セキュリティグループのポート 22 を削除
c5d3e4f chore: 本番環境用 VPC CIDR を更新
再現性・一貫性
開発・ステージング・本番で同一のコードを使うため、「開発では動いたのに本番で動かない」という環境差異を防げます。
チーム開発の民主化
インフラ変更もアプリコードと同様に Pull Request でレビューできます。インフラエンジニア以外もコードを読んで理解・提案できるようになります。
1-2. Terraform の位置づけ
数あるIaCツールの中で Terraform はどのような立ち位置にあるのでしょうか。主要ツールを比較します。
主要 IaC ツール比較
| ツール | 記述言語 | マルチクラウド | 状態管理 | 特徴 |
|---|---|---|---|---|
| Terraform | HCL(独自DSL) | ◎ 対応 | tfstate ファイル | プロバイダーエコシステムが豊富。宣言的記述でシンプル |
| AWS CloudFormation | YAML / JSON | ✗ AWS専用 | AWS 側で管理 | AWS ネイティブ。無料。AWS サービスへの対応が最速 |
| AWS CDK | TypeScript / Python 等 | △ 主にAWS | CloudFormation 経由 | プログラミング言語で記述。ロジックを組み込みやすい |
| Pulumi | TypeScript / Python / Go 等 | ◎ 対応 | Pulumi Service / S3 等 | 既存プログラミング言語を使えるため学習コストが低い |
Terraform が選ばれる理由
1. マルチクラウド対応
AWS・Azure・GCP を同一ツールで管理できます。クラウドを横断したインフラ管理が必要な企業で特に重宝されます。
2. 豊富なプロバイダーエコシステム
2026年時点で Terraform Registry には 4,000以上のプロバイダー が公開されています。AWS・Azure・GCP はもちろん、Datadog・GitHub・Snowflake など SaaS サービスもコードで管理できます。
3. 宣言的記述によるシンプルさ
「どのような状態にしたいか」を記述するだけで、Terraform が現状との差分を計算して必要な操作を実行します。「何をすべきか」(手続き的)ではなく「どうあるべきか」(宣言的)を書くだけでよい点がシンプルです。
4. 実績と安定性
HashiCorp が開発し、2014年のリリース以降 10年以上の実績を持ちます。エンタープライズ採用実績も豊富で、コミュニティも大きく情報が充実しています。
補足: OpenTofu について
2023年に HashiCorp が Terraform のライセンスを BSL に変更したため、OSS フォークとして OpenTofu が誕生しました。基本的な HCL 構文は Terraform と互換性があります。本記事では Terraform(HashiCorp 版)を対象とします。
1-3. Terraform のアーキテクチャ
Terraform を理解する上で核心となる 5つのコア概念 を押さえましょう。
5つのコア概念
┌─────────────────────────────────────────────────────────────────┐
│Terraform Core│
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Provider │ │ Resource │ │ State│ │ Plan / │ │
│ │ │ │ │ │ (tfstate)│ │ Apply│ │
│ │ AWS・ │ │ S3 Bucket│ │ 現状記録 │ │ 差分計算 │ │
│ │ Azure等 │ │ EC2等 │ │ │ │ & 実行│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ AWS API │ │ terraform│
│ Azure API│ │ .tfstate │
│ GCP API 等 │ │ (S3等に保存) │
└─────────────┘ └──────────────┘
Provider(プロバイダー)
クラウドプロバイダーや SaaS との通信を担うプラグインです。hashicorp/aws プロバイダーは AWS API を呼び出すための認証・エンドポイント情報を管理します。
provider "aws" {
region = "ap-northeast-1"
}
Resource(リソース)
Terraform で管理する実際のインフラリソースです。resource "リソースタイプ" "ローカル名" の形式で定義します。
resource "aws_s3_bucket" "handson" {
bucket = "terraform-handson-bucket-YOURNAME"
}
State(ステート)
現在のインフラ状態を記録する JSON ファイル(terraform.tfstate)です。Terraform は State を参照して「現状と定義の差分」を計算します。
- ローカル保存: デフォルトで
terraform.tfstateファイルに保存 - リモート保存: S3 + DynamoDB(チーム開発推奨)
State ファイルの取り扱い注意点
tfstate ファイルにはリソースの詳細情報(場合によっては認証情報)が含まれます。.gitignoreに追加して Git リポジトリに含めないようにしましょう。
Plan(プラン)
terraform plan を実行すると、現在の State と .tf ファイルの差分を計算し、「何を作成・変更・削除するか」をプレビュー表示します。実際のリソース変更は行わない dry-run です。
Plan: 1 to add, 0 to change, 0 to destroy.
+ aws_s3_bucket.handson
bucket: "terraform-handson-bucket-YOURNAME"
Apply(アプライ)
terraform apply を実行すると、Plan の内容を実際に AWS API 経由で適用します。リソースの作成・変更・削除が実行されます。
Terraform ワークフロー
[初回セットアップ]
terraform init
│
▼(プロバイダーのダウンロード・初期化)
terraform plan
│
▼(差分のプレビュー確認)
terraform apply
│
▼(リソース作成・State 更新)
[変更時]
.tf ファイルを編集
│
▼
terraform plan ← 差分確認(必須)
│
▼
terraform apply ← 変更適用
[リソース削除時]
terraform destroy ← 全リソース削除(注意!)
| コマンド | 役割 | 実際の変更 |
|---|---|---|
terraform init | プロバイダーDL・バックエンド初期化 | なし |
terraform plan | 差分計算・プレビュー | なし(dry-run) |
terraform apply | プランを実行してリソース作成/変更/削除 | あり |
terraform destroy | 管理下の全リソースを削除 | あり(破壊的) |
terraform fmt | HCL コードを自動整形 | なし |
terraform validate | 構文・設定の検証 | なし |
terraform output | output 変数の値を表示 | なし |
1-4. HCL 基本構文
Terraform の設定ファイルは HCL(HashiCorp Configuration Language) で記述します。JSON に似た読みやすい構文です。
terraform ブロック
Terraform 本体のバージョン要件とプロバイダーの依存を宣言します。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
2026年時点の最新バージョン
– Terraform: 1.14.8(2026年3月25日リリース)
– AWS Provider: 6.40.0(2026年4月時点)本ハンズオンでは
required_version = ">= 1.7.0"と指定し、Terraform 1.7以降で動作確認しています。本番環境では= 1.14.8のように固定バージョンを推奨します。
provider ブロック
接続先クラウドの設定(リージョン・認証情報等)を記述します。
provider "aws" {
region = "ap-northeast-1"
}
複数リージョンを使う場合は alias で区別できます。
provider "aws" {
alias = "tokyo"
region = "ap-northeast-1"
}
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
resource ブロック
管理対象リソースを定義します。書式は resource "リソースタイプ" "ローカル名" です。
resource "aws_s3_bucket" "handson" {
bucket = "terraform-handson-bucket-YOURNAME"
tags = {
Name = "terraform-handson"
Environment = "dev"
}
}
aws_s3_bucket: リソースタイプ(プロバイダー名_リソース名)handson: Terraform 内でこのリソースを参照するためのローカル名- 他のリソースから
aws_s3_bucket.handson.idのように参照できます
variable ブロック
入力変数を定義します。環境ごとに値を変えたい場合に使います。
variable "bucket_name" {
type = string
description = "S3バケット名"
default = "terraform-handson-bucket"
}
variable "environment" {
type = string
description = "環境名(dev/stg/prod)"
# default なし → 実行時に必須入力
}
variable "instance_count" {
type = number
default = 1
}
変数の参照には var.変数名 を使います。
resource "aws_s3_bucket" "handson" {
bucket = var.bucket_name
}
変数への値渡し方法:
– terraform.tfvars ファイルに記述
– -var "bucket_name=my-bucket" オプション
– 環境変数 TF_VAR_bucket_name=my-bucket
output ブロック
terraform apply 後に出力したい値を定義します。他のモジュールへの値渡しにも使います。
output "bucket_id" {
value = aws_s3_bucket.handson.id
description = "作成したS3バケットのID"
}
output "bucket_arn" {
value = aws_s3_bucket.handson.arn
description = "作成したS3バケットのARN"
sensitive= false
}
実行後に terraform output bucket_id で個別に参照できます。
data ブロック
既に存在するリソース(Terraform 管理外含む)の情報を参照します。
# 既存の VPC を名前で検索
data "aws_vpc" "existing" {
filter {
name= "tag:Name"
values = ["production-vpc"]
}
}
# 参照は data.リソースタイプ.ローカル名.属性
resource "aws_subnet" "app" {
vpc_id = data.aws_vpc.existing.id
cidr_block = "10.0.1.0/24"
}
data ブロックは apply 時にリソースを作成しません。既存インフラの情報を読み込むだけです。
locals ブロック
モジュール内でのみ使う内部変数(ローカル変数)を定義します。繰り返し使う値や計算式を一か所にまとめられます。
locals {
project = "terraform-handson"
environment = "dev"
common_tags = {
Project = local.project
Environment = local.environment
ManagedBy= "Terraform"
}
}
resource "aws_s3_bucket" "handson" {
bucket = "${local.project}-bucket"
tags= local.common_tags
}
local.変数名 で参照します(var. との違いに注意)。
HCL 構文まとめ
| ブロック | 用途 | 参照方法 |
|---|---|---|
terraform | Terraform 本体・プロバイダーのバージョン宣言 | — |
provider | クラウド接続設定 | — |
resource | リソース定義(作成・管理) | リソースタイプ.ローカル名.属性 |
variable | 入力変数(外部から値を渡す) | var.変数名 |
output | 出力変数(値を外部に公開) | terraform output 変数名 |
data | 既存リソースの参照(読み取り専用) | data.リソースタイプ.ローカル名.属性 |
locals | 内部変数(モジュール内のみ) | local.変数名 |
1-5. Terraform のバージョン管理
チーム開発では Terraform のバージョンを揃えること が非常に重要です。バージョンによって構文や動作が変わる場合があるためです。
required_version の指定方法
terraform {
# 完全一致(厳格)
required_version = "= 1.14.8"
# 最小バージョン(1.7.0以上)
required_version = ">= 1.7.0"
# 悲観的制約(1.xシリーズ内の最新を許容)
required_version = "~> 1.7"
# 範囲指定
required_version = ">= 1.7.0, < 2.0.0"
}
| 演算子 | 意味 | 例 |
|---|---|---|
= | 完全一致 | = 1.14.8 → 1.14.8 のみ |
>= | 以上 | >= 1.7.0 → 1.7.0 以上すべて |
~> | 悲観的制約(マイナーまで固定) | ~> 1.7 → 1.7.x のみ(2.0 は不可) |
~> | 悲観的制約(パッチまで固定) | ~> 1.7.0 → 1.7.0〜1.7.x のみ |
チーム開発では = 1.14.8 のように完全一致指定が最も安全です。CI/CD パイプラインでもバージョンを固定することを推奨します。
tfenv によるバージョン管理
tfenv は Terraform のバージョン管理ツールです。Node.js の nvm に相当します。
# インストール(macOS)
brew install tfenv
# 利用可能なバージョン一覧
tfenv list-remote
# 特定バージョンをインストール
tfenv install 1.14.8
# バージョンを切り替え
tfenv use 1.14.8
# 現在のバージョンを確認
terraform version
プロジェクトルートに .terraform-version ファイルを置くと、そのディレクトリでのバージョンが固定されます。
# .terraform-version
1.14.8
asdf でも Terraform を管理できます。
# asdf で Terraform プラグイン追加
asdf plugin add terraform
# バージョンインストール
asdf install terraform 1.14.8
# プロジェクト固定
asdf local terraform 1.14.8
terraform.lock.hcl の役割
terraform init を実行すると、プロジェクトルートに terraform.lock.hcl が生成されます。
# terraform.lock.hcl(自動生成)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.94.0"
constraints = "~> 5.0"
hashes = [
"h1:xxxx...",
"zh:yyyy...",
]
}
lock ファイルの役割
- プロバイダーの バージョンとハッシュを固定 する
- チームメンバー全員が同一バージョンのプロバイダーを使えるよう保証する
- セキュリティ面でもプロバイダーの改ざんを防止できる
重要: terraform.lock.hcl は Git にコミットしてください(terraform.tfstate とは逆)。
# .gitignore(推奨設定)
*.tfstate
*.tfstate.backup
.terraform/ # プロバイダーバイナリ(再取得可能)
# コミットすべきファイル
# terraform.lock.hcl ← コメントアウト不要(コミット対象)
バージョン管理のベストプラクティスまとめ
プロジェクト
├── .terraform-version← tfenv/asdf 用バージョン固定
├── terraform.lock.hcl← Git にコミット(プロバイダー固定)
├── .gitignore ← .terraform/ と *.tfstate を除外
└── versions.tf ← required_version を専用ファイルに分離
# versions.tf(バージョン宣言を専用ファイルに分離する慣習)
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Section 1 まとめ
| テーマ | ポイント |
|---|---|
| IaC | インフラをコードで管理。再現性・バージョン管理・チーム開発が向上 |
| Terraform の位置づけ | マルチクラウド対応・豊富なプロバイダー・宣言的HCL |
| 5つのコア概念 | Provider / Resource / State / Plan / Apply |
| ワークフロー | init → plan → apply → (変更時 plan→apply) → destroy |
| HCL ブロック | terraform / provider / resource / variable / output / data / locals |
| バージョン管理 | required_version・tfenv・terraform.lock.hcl のセット管理 |
次の Section 2 では、Terraform の内部アーキテクチャとワークフローを図解で解説します。
Section 2: アーキテクチャ解説
Terraform を使った AWS インフラ構築を始める前に、その内部構造と動作原理を理解しておくことが重要です。このセクションでは、Terraform のワークフロー全体像・State 管理の仕組み・Provider と Resource の関係という3つの観点からアーキテクチャを解説します。
2-1. Terraform ワークフロー全体図

Terraform の基本的な作業フローは、init → plan → apply という3ステップで構成されています。destroy はリソース削除時にのみ実行する逆方向のオペレーションです。
init フェーズ
terraform init
terraform init は、作業ディレクトリを初期化するコマンドです。具体的には以下の処理を行います。
required_providersブロックに記述されたプロバイダプラグインのダウンロード(.terraform/ディレクトリに保存)- バックエンド(State 保存先)の初期化
- モジュールの取得
.terraform/ ディレクトリにはプロバイダバイナリが保存されます。このディレクトリは .gitignore に追加し、バージョン管理から除外してください。
plan フェーズ
terraform plan
terraform plan は、現在の .tf ファイルと既存の State を比較し、実行計画(差分) を出力します。実際のリソース変更は行いません。
出力例:
Plan: 3 to add, 0 to change, 0 to destroy.
このフェーズでは +(追加)・~(変更)・-(削除)の記号で変更内容が表示されます。本番環境への適用前に必ず確認しましょう。
apply フェーズ
terraform apply
terraform apply は、plan の内容を実際に AWS に対して実行します。デフォルトでは確認プロンプトが表示されます(-auto-approve オプションで省略可能ですが、CI 以外では非推奨)。
apply 完了後、terraform.tfstate ファイルに現在のリソース状態が記録されます。
destroy フェーズ
terraform destroy
terraform destroy は、管理下のリソースをすべて削除します。apply の逆操作です。コスト削減や開発環境のクリーンアップに使用します。
注意: destroy は元に戻せません。必ず
terraform plan -destroyで事前確認してください。
2-2. State 管理の仕組み

State とは
Terraform の State(状態ファイル) は、Terraform が管理するリソースの現在の状態を JSON 形式で記録したファイルです。デフォルトでは terraform.tfstate という名前でローカルに保存されます。
{
"version": 4,
"terraform_version": "1.7.0",
"resources": [
{
"type": "aws_s3_bucket",
"name": "my-bucket",
"instances": [...]
}
]
}
ローカル State の問題点
チーム開発においてローカル State は以下の問題を引き起こします。
| 問題 | 詳細 |
|---|---|
| 競合 | 複数の開発者が同時に apply を実行すると State が上書きされる |
| 不整合 | ローカルの State が最新でない場合、誤った差分が計算される |
| 共有困難 | State ファイルを Git にコミットするとシークレット情報が漏洩するリスクがある |
リモート State の仕組み(S3 + DynamoDB)
チーム開発では リモートバックエンド を使用して State を共有します。AWS では S3 + DynamoDB の組み合わせが標準的です。
terraform {
backend "s3" {
bucket= "my-terraform-state"
key= "prod/terraform.tfstate"
region= "ap-northeast-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
S3 の役割: State ファイルの永続保存とバージョニング(誤操作からの復旧)
DynamoDB の役割: State ロック(同時実行の防止)
DynamoDB によるロック機構により、あるユーザーが terraform apply を実行中は他のユーザーの apply がブロックされます。これにより State の競合を防ぎます。
Lock Info:
ID: a1b2c3d4-...
Path:my-terraform-state/prod/terraform.tfstate
Operation: OperationTypeApply
Who: user@machine
Created:2026-04-13T10:00:00Z
State ファイルの取り扱い注意事項
.gitignoreに必ず追加: パスワードや秘密鍵が平文で含まれることがあるterraform.tfstate.backup: apply 前の State が自動バックアップされる- 直接編集禁止:
terraform stateコマンドを使用すること
2-3. Provider と Resource の関係

Provider とは
Provider は、Terraform と外部サービス(AWS・GCP・Azure など)の間を仲介するプラグインです。AWS Provider(hashicorp/aws)は、AWS の各種サービスに対する CRUD 操作を Terraform の宣言的構文で利用できるようにします。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
Provider は Terraform Registry で公開されており、terraform init 時に自動ダウンロードされます。
Resource とは
Resource は、Terraform で管理するインフラの最小単位です。resource ブロックで定義します。
resource "aws_s3_bucket" "my-bucket" {
bucket = "my-unique-bucket-name"
tags = {
Environment = "production"
}
}
resource "aws_instance" "web-server" {
ami = "ami-0c3fd0f5d33134a76"
instance_type = "t3.micro"
}
resource "aws_security_group" "allow-http" {
name = "allow-http"
description = "Allow HTTP inbound traffic"
ingress {
from_port= 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Provider → Resource → AWS の関係フロー
Terraform 設定 (.tf)
│
├── Provider (hashicorp/aws) ← API認証・エンドポイント設定
│ │
│ └── AWS API を抽象化
│
└── Resource 定義
│
├── aws_s3_bucket → S3 バケット (AWS)
├── aws_instance → EC2 インスタンス (AWS)
└── aws_security_group → Security Group (AWS)
Provider が AWS API の認証情報(リージョン・クレデンシャル)を管理し、各 Resource がそのコンテキストで AWS に対して API コールを行います。
Resource 間の依存関係
Resource 間の依存関係は depends_on で明示するか、属性参照(aws_instance.web-server.id など)で暗黙的に解決されます。
resource "aws_instance" "web-server" {
ami = "ami-0c3fd0f5d33134a76"
instance_type= "t3.micro"
security_groups = [aws_security_group.allow-http.name] # 暗黙的依存
}
Terraform は依存グラフを自動解析し、依存関係のないリソースは並列で作成します。これにより大規模インフラの構築時間を短縮できます。
Section 2 まとめ
| 概念 | 役割 |
|---|---|
| ワークフロー | init → plan → apply → (destroy) の4フェーズ |
| State | リソースの現在状態を記録。チーム開発では S3+DynamoDB でリモート管理 |
| Provider | AWS API を抽象化するプラグイン。hashicorp/aws が AWS 操作の基盤 |
| Resource | 管理する AWS リソースの宣言(S3・EC2・SG など) |
次のセクションでは、実際に AWS マネジメントコンソールを使って Terraform の実行環境をセットアップし、最初のリソースをデプロイします。
Section 3: ハンズオン — ゼロからTerraformで AWS 環境を構築する
所要時間の目安: 約 90〜120 分
前提条件: AWSアカウント(無料利用枠可)、ターミナル操作の基礎知識
このセクションでは、Step A〜E の 5 段階で Terraform の基本操作を体験します。
最初はシンプルな S3 バケット作成から始まり、最後はリモート State 管理まで、実務で必要なスキルを一通り習得できます。
3-1. 環境準備
Terraform のインストール
Terraform は HashiCorp が提供する IaC ツールです。2026 年 4 月時点の最新安定版は v1.14.8(2026-03-25 リリース)です。
Mac(Homebrew)
# HashiCorp の tap を追加して Terraform をインストール
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# すでにインストール済みの場合はアップグレード
brew upgrade hashicorp/tap/terraform
Windows(Chocolatey)
管理者権限で PowerShell を起動し、以下を実行します。
choco install terraform
Chocolatey が未インストールの場合: https://chocolatey.org/install の手順に従ってインストールしてください。
Linux(Ubuntu / Debian)
# 必要パッケージのインストール
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
# HashiCorp の GPG キーを追加
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null
# HashiCorp のリポジトリを追加
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
# リポジトリを更新して Terraform をインストール
sudo apt-get update
sudo apt-get install terraform
バージョン確認
インストール後、以下のコマンドでバージョンを確認します。
terraform version
正常にインストールされていれば、以下のような出力が得られます。
Terraform v1.14.8
on darwin_arm64
注意: バージョンが
>= 1.7.0であれば、このハンズオンの全手順を実行できます。
AWS CLI の設定
Terraform が AWS リソースを操作するために、AWS CLI の設定が必要です。
IAM ユーザーの作成
- AWS マネジメントコンソール にログイン
- IAM → ユーザー → ユーザーを作成 をクリック
- ユーザー名を入力(例:
terraform-handson) - 次のステップ: 許可の設定 で ポリシーを直接アタッチする を選択
AdministratorAccessを選択して ユーザーの作成- セキュリティ認証情報 タブ → アクセスキーを作成
- コマンドラインインターフェイス (CLI) を選択してアクセスキーを作成
- アクセスキー ID と シークレットアクセスキー をメモ(後で使用)
本番環境では: AdministratorAccess ではなく最小権限の IAM ポリシーを使用してください。ハンズオン目的で一時的に利用し、終了後はアクセスキーを削除することを推奨します。
aws configure の実行
aws configure
対話形式でアクセス情報を入力します。
AWS Access Key ID [None]: AKIA...(先ほどメモしたアクセスキー ID)
AWS Secret Access Key [None]: xxxxxxxx(シークレットアクセスキー)
Default region name [None]: ap-northeast-1
Default output format [None]: json
設定が正しいか確認します。
aws sts get-caller-identity
以下のように自分のアカウント情報が表示されれば成功です。
{
"UserId": "AIDA...",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/terraform-handson"
}
作業ディレクトリの構成
このハンズオンでは、以下のディレクトリ構成で作業を進めます。
~/terraform-handson/
├── step-a/ # Step A: S3バケット作成
│└── main.tf
├── step-b/ # Step B: EC2インスタンス作成
│├── main.tf
│└── ec2.tf
├── step-c/ # Step C: 変数化
│├── main.tf
│├── variables.tf
│└── terraform.tfvars
├── step-d/ # Step D: Output
│├── main.tf
│├── variables.tf
│├── terraform.tfvars
│└── outputs.tf
├── step-e/ # Step E: リモートState設定
│├── main.tf
│├── variables.tf
│├── terraform.tfvars
│├── outputs.tf
│└── backend.tf
└── state-backend/# Step E: State管理用リソース
└── main.tf
作業ディレクトリを作成します。
mkdir -p ~/terraform-handson/{step-a,step-b,step-c,step-d,step-e,state-backend}
3-2. Step A: Hello Terraform — S3 バケット作成
最初のステップとして、Terraform の基本的なワークフロー(init → plan → apply → destroy)を S3 バケットの作成を通じて体験します。
cd ~/terraform-handson/step-a
main.tf の作成
以下の内容で main.tf を作成します。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "handson" {
bucket = "terraform-handson-bucket-YOURNAME"
tags = {
Name = "terraform-handson"
Environment = "dev"
}
}
重要:
terraform-handson-bucket-YOURNAMEのYOURNAME部分を自分のユニークな文字列(例: 名前 + 日付)に変更してください。S3 バケット名はグローバルで一意である必要があります。
terraform init
プロバイダープラグインをダウンロードし、作業ディレクトリを初期化します。
terraform init
初期化が成功すると、以下のような出力が表示されます。
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.94.1...
- Installed hashicorp/aws v5.94.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your configuration. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
初期化後、ディレクトリには以下のファイルが追加されます。
step-a/
├── main.tf
├── .terraform/ # プロバイダーバイナリが格納される
│└── providers/
│ └── registry.terraform.io/
│ └── hashicorp/
│└── aws/
└── .terraform.lock.hcl # プロバイダーバージョンのロックファイル
terraform plan
実際にリソースを作成する前に、変更計画を確認します。
terraform plan
以下のような出力が表示されます。
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.handson will be created
+ resource "aws_s3_bucket" "handson" {
+ acceleration_status= (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "terraform-handson-bucket-YOURNAME"
+ bucket_domain_name = (known after apply)
+ bucket_prefix= (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy= false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled= (known after apply)
+ region = (known after apply)
+ request_payer= (known after apply)
+ tags= {
+ "Environment" = "dev"
+ "Name" = "terraform-handson"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "terraform-handson"
}
+ website_domain = (known after apply)
+ website_endpoint= (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
──────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to perform exactly these actions if you run "terraform apply" now.
Plan: 1 to add と表示され、S3 バケット 1 つが追加されることが確認できます。
terraform apply
計画を実際に適用して、AWS リソースを作成します。
terraform apply
plan と同じ内容が表示された後、確認プロンプトが表示されます。
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
yes と入力して Enter を押します。
aws_s3_bucket.handson: Creating...
aws_s3_bucket.handson: Creation complete after 2s [id=terraform-handson-bucket-YOURNAME]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
AWS コンソールで確認
- AWS S3 コンソール を開く
- 東京リージョン(ap-northeast-1)を選択
terraform-handson-bucket-YOURNAMEが作成されていることを確認
作成されたバケットをクリックすると、プロパティ タブで terraform-handson タグが付与されていることも確認できます。
terraform destroy
ハンズオン終了後のリソース削除を体験します。Step B 以降でこのリソースは使用しないため、ここで削除します。
terraform destroy
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_s3_bucket.handson will be destroyed
- resource "aws_s3_bucket" "handson" {
- arn = "arn:aws:s3:::terraform-handson-bucket-YOURNAME" -> null
- bucket = "terraform-handson-bucket-YOURNAME" -> null
...
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:
yes と入力します。
aws_s3_bucket.handson: Destroying... [id=terraform-handson-bucket-YOURNAME]
aws_s3_bucket.handson: Destruction complete after 1s
Destroy complete! Resources: 1 destroyed.
Step A のポイント:
– terraform init でプロバイダーをダウンロード(プロジェクト初回のみ)
– terraform plan で変更内容を事前確認(常に実行する習慣をつける)
– terraform apply でリソースを作成(yes の確認が必要)
– terraform destroy でリソースを削除(本番環境では慎重に)
3-3. Step B: EC2 インスタンス作成
Step B では、セキュリティグループ・キーペア・EC2 インスタンスの 3 つのリソースを作成し、リソース間の依存関係を学びます。
cd ~/terraform-handson/step-b
ファイル構成
Step B では 2 つのファイルを使用します。
step-b/
├── main.tf# terraform ブロック + provider ブロック(Step A と同じ)
└── ec2.tf # セキュリティグループ・キーペア・EC2リソース
main.tf の作成
Step A と同じ内容の main.tf を作成します。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "handson" {
bucket = "terraform-handson-bucket-YOURNAME"
tags = {
Name = "terraform-handson"
Environment = "dev"
}
}
ec2.tf の作成
以下の内容で ec2.tf を作成します。
resource "aws_security_group" "handson_sg" {
name = "terraform-handson-sg"
description = "Allow SSH"
ingress {
from_port= 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"]
}
egress {
from_port= 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_key_pair" "handson" {
key_name= "terraform-handson-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "aws_instance" "handson_ec2" {
ami = "ami-0b5c74e235ed808b9"
instance_type = "t3.micro"
key_name= aws_key_pair.handson.key_name
vpc_security_group_ids = [aws_security_group.handson_sg.id]
tags = {
Name = "terraform-handson-ec2"
}
}
YOUR_IP の確認:
curl ifconfig.meまたは https://checkip.amazonaws.com でグローバル IP アドレスを確認し、YOUR_IP/32に設定してください。
セキュリティのため、SSH のインバウンドルールは自分の IP のみに制限することを強く推奨します。AMI ID について:
ami-0b5c74e235ed808b9は ap-northeast-1 の Amazon Linux 2023 AMI です。最新の AMI ID は AWS コンソールの EC2 → AMI カタログ で “Amazon Linux 2023” を検索するか、以下の AWS CLI コマンドで確認してください。bash
aws ssm get-parameter \
--name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--region ap-northeast-1 \
--query "Parameter.Value" \
--output textSSH キーペアの準備:
~/.ssh/id_rsa.pubが存在しない場合は、以下のコマンドで作成してください。bash
ssh-keygen -t rsa -b 4096 -C "terraform-handson"
リソース間の依存関係
ec2.tf の aws_instance リソースを注目してください。
key_name= aws_key_pair.handson.key_name
vpc_security_group_ids = [aws_security_group.handson_sg.id]
aws_key_pair.handson.key_name と aws_security_group.handson_sg.id という参照を使用しています。
Terraform はこの参照を解析して、暗黙的な依存関係(Implicit Dependency) を自動的に検出します。
つまり、EC2 インスタンスを作成する前に必ずキーペアとセキュリティグループが作成されます。
Terraform が依存関係グラフ(Dependency Graph)を構築し、正しい順序でリソースを作成・削除します。
terraform init & plan
terraform init
terraform plan
plan では 4 つのリソース(S3 バケット + セキュリティグループ + キーペア + EC2)が追加対象として表示されます。
Plan: 4 to add, 0 to change, 0 to destroy.
terraform apply
terraform apply
yes と入力して適用します。
aws_key_pair.handson: Creating...
aws_security_group.handson_sg: Creating...
aws_s3_bucket.handson: Creating...
aws_key_pair.handson: Creation complete after 0s [id=terraform-handson-key]
aws_s3_bucket.handson: Creation complete after 2s [id=terraform-handson-bucket-YOURNAME]
aws_security_group.handson_sg: Creation complete after 2s [id=sg-0xxxxxxxxxxxxxxx]
aws_instance.handson_ec2: Creating...
aws_instance.handson_ec2: Still creating... [10s elapsed]
aws_instance.handson_ec2: Still creating... [20s elapsed]
aws_instance.handson_ec2: Creation complete after 22s [id=i-0xxxxxxxxxxxxxxxx]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
S3 バケットとキーペア・セキュリティグループは並列に作成され、EC2 はそれらの完了後に作成されることが分かります。
SSH 接続確認
EC2 の パブリック IP アドレスを確認します。
terraform show | grep public_ip
または、AWS コンソールの EC2 → インスタンス から確認します。
[スクリーンショット: EC2 インスタンス一覧、terraform-handson-ec2 が running 状態]SSH 接続を試みます。
ssh -i ~/.ssh/id_rsa ec2-user@<パブリックIP>
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~/
~~._._/
_/ _/
_/m/'
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$
接続できたら、exit で切断します。
接続できない場合: セキュリティグループの
cidr_blocksに設定した IP アドレスが正しいか確認してください。
Step C に進む前に、リソースを削除してコストを抑えます。
terraform destroy
3-4. Step C: 変数化
Step C では、ハードコードされた値を 変数(Variables) に置き換え、環境ごとに異なる設定を柔軟に管理できるようにします。
cd ~/terraform-handson/step-c
ファイル構成
step-c/
├── main.tf# S3バケット・EC2リソース(var.xxx 参照に更新)
├── variables.tf # 変数定義
└── terraform.tfvars# 変数の値
variables.tf の作成
variable "aws_region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
variable "environment" {
description = "環境名 (dev/stg/prod)"
type = string
default = "dev"
}
variable "bucket_name" {
description = "S3バケット名(グローバル一意)"
type = string
}
variable "instance_type" {
description = "EC2インスタンスタイプ"
type = string
default = "t3.micro"
}
bucket_name には default がありません。これは必須変数(Required Variable)であり、terraform.tfvars や CLI オプションで必ず値を指定する必要があります。
terraform.tfvars の作成
bucket_name= "terraform-handson-bucket-YOURNAME"
environment= "dev"
instance_type = "t3.micro"
YOURNAME を自分の識別子に変更してください。
aws_region は variables.tf の default 値(ap-northeast-1)が使われるため、tfvars への記載は省略しています。
main.tf の作成(変数参照版)
Step B の main.tf + ec2.tf を 1 ファイルにまとめ、変数を参照するように更新します。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_s3_bucket" "handson" {
bucket = var.bucket_name
tags = {
Name = "terraform-handson"
Environment = var.environment
}
}
resource "aws_security_group" "handson_sg" {
name = "terraform-handson-sg"
description = "Allow SSH"
ingress {
from_port= 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"]
}
egress {
from_port= 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_key_pair" "handson" {
key_name= "terraform-handson-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "aws_instance" "handson_ec2" {
ami = "ami-0b5c74e235ed808b9"
instance_type = var.instance_type
key_name= aws_key_pair.handson.key_name
vpc_security_group_ids = [aws_security_group.handson_sg.id]
tags = {
Name = "terraform-handson-ec2"
Environment = var.environment
}
}
変数の優先順位
Terraform の変数は、以下の優先順位で値が決定されます(上位が優先)。
| 優先順位 | 指定方法 | 例 |
|---|---|---|
| 1 | -var CLI オプション | terraform apply -var="environment=prod" |
| 2 | -var-file CLI オプション | terraform apply -var-file="prod.tfvars" |
| 3 | *.auto.tfvars / terraform.tfvars | terraform.tfvars に記述 |
| 4 | 環境変数 TF_VAR_xxx | export TF_VAR_environment=prod |
| 5 | default 値 | variables.tf の default |
dev / prod 環境分離の実践
terraform.tfvars を環境ごとに分けることで、dev/prod 環境の設定を管理できます。
step-c/
├── main.tf
├── variables.tf
├── dev.tfvars # 開発環境の変数値
└── prod.tfvars # 本番環境の変数値
dev.tfvars
bucket_name= "terraform-handson-bucket-YOURNAME-dev"
environment= "dev"
instance_type = "t3.micro"
prod.tfvars
bucket_name= "terraform-handson-bucket-YOURNAME-prod"
environment= "prod"
instance_type = "t3.small"
環境を切り替えてデプロイします。
# 開発環境
terraform apply -var-file="dev.tfvars"
# 本番環境
terraform apply -var-file="prod.tfvars"
terraform plan で変数の確認
terraform init
terraform plan
plan 出力で var.bucket_name が terraform.tfvars の値に置き換えられていることを確認できます。
+ resource "aws_s3_bucket" "handson" {
+ bucket = "terraform-handson-bucket-YOURNAME"
...
+ tags= {
+ "Environment" = "dev"
+ "Name" = "terraform-handson"
}
}
確認できたら terraform apply を実行してください。Step D に進む際にはリソースをそのまま残しておきます(destroy は不要です)。
3-5. Step D: Output
Step D では、terraform apply 後に重要な情報(バケット名・EC2 の IP など)を Output(出力値) として定義し、簡単に参照できるようにします。
cd ~/terraform-handson/step-d
ファイル構成
Step C のファイルをコピーして outputs.tf を追加します。
step-d/
├── main.tf# Step C と同じ
├── variables.tf # Step C と同じ
├── terraform.tfvars# Step C と同じ
└── outputs.tf# 出力値の定義(新規)
outputs.tf の作成
output "s3_bucket_name" {
description = "S3バケット名"
value = aws_s3_bucket.handson.id
}
output "s3_bucket_arn" {
description = "S3バケットARN"
value = aws_s3_bucket.handson.arn
}
output "ec2_public_ip" {
description = "EC2パブリックIP"
value = aws_instance.handson_ec2.public_ip
}
output "ec2_instance_id" {
description = "EC2インスタンスID"
value = aws_instance.handson_ec2.id
}
terraform apply
terraform init
terraform apply
apply の最後に Output の値が表示されます。
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
ec2_instance_id = "i-0xxxxxxxxxxxxxxxx"
ec2_public_ip= "54.xxx.xxx.xxx"
s3_bucket_arn= "arn:aws:s3:::terraform-handson-bucket-YOURNAME"
s3_bucket_name = "terraform-handson-bucket-YOURNAME"
terraform output コマンド
apply 後いつでも Output を確認できます。
# 全 Output を表示
terraform output
# 特定の Output のみ表示
terraform output ec2_public_ip
# JSON 形式で出力(スクリプト連携に便利)
terraform output -json
# ec2_public_ip のみ表示した場合
$ terraform output ec2_public_ip
"54.xxx.xxx.xxx"
# -raw オプションで引用符なしの値を取得
$ terraform output -raw ec2_public_ip
54.xxx.xxx.xxx
-raw オプションの活用例: SSH 接続をワンライナーで実行できます。
ssh -i ~/.ssh/id_rsa ec2-user@$(terraform output -raw ec2_public_ip)
Output の活用シーン
Output は単なる確認機能ではなく、モジュール間のデータ連携にも使われます。
例えば、ネットワークモジュールが出力した VPC ID を、EC2 モジュールが参照するといったパターンです。
# モジュール参照の例(モジュールはSection 4で詳しく解説)
module "network" {
source = "./modules/network"
}
resource "aws_instance" "app" {
subnet_id = module.network.public_subnet_id # モジュールのOutput を参照
...
}
Step D のリソースはそのまま残して Step E に進みます。
3-6. Step E: リモート State 設定
Step E では、ローカルに保存されていた State ファイルを S3 + DynamoDB に移行し、チームでの Terraform 運用に必要なリモート State 管理を実装します。
なぜリモート State が必要なのか?
ローカル State(terraform.tfstate)には以下の問題があります。
| 問題 | 内容 |
|---|---|
| チーム共有不可 | 各自の PC に State があると、他のメンバーの変更が反映されない |
| 競合リスク | 複数人が同時に terraform apply すると State が壊れる |
| 消失リスク | PC 障害や誤削除で State ファイルが失われる |
| 機密情報 | State には パスワードなどの機密情報が含まれる場合がある |
S3 + DynamoDB を使ったリモート State では:
– S3 で State ファイルを安全に保管(バージョニング有効)
– DynamoDB でロック管理(同時実行を防ぐ)
State 管理用リソースの事前作成
S3 バックエンドを設定する前に、State を保存する S3 バケットと DynamoDB テーブルを別のディレクトリで作成します。
cd ~/terraform-handson/state-backend
以下の内容で main.tf を作成します。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "tfstate" {
bucket = "terraform-state-YOURNAME"
tags = {
Name = "terraform-state"
}
}
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "tfstate_lock" {
name= "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "terraform-state-lock"
}
}
terraform init
terraform apply
aws_s3_bucket.tfstate: Creating...
aws_s3_bucket.tfstate: Creation complete after 2s [id=terraform-state-YOURNAME]
aws_s3_bucket_versioning.tfstate: Creating...
aws_s3_bucket_versioning.tfstate: Creation complete after 1s [id=terraform-state-YOURNAME]
aws_dynamodb_table.tfstate_lock: Creating...
aws_dynamodb_table.tfstate_lock: Creation complete after 6s [id=terraform-state-lock]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
[スクリーンショット: AWS コンソールの DynamoDB テーブル一覧、terraform-state-lock が ACTIVE 状態]Step E: backend.tf の設定
cd ~/terraform-handson/step-e
Step D のファイルをすべてコピーして、main.tf の terraform ブロックを以下に差し替えます(backend "s3" ブロックを追加)。
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket= "terraform-state-YOURNAME"
key= "handson/terraform.tfstate"
region= "ap-northeast-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
YOURNAME を自分の識別子に変更してください(state-backend で使用した名前と一致させること)。
各設定の意味:
| 設定項目 | 内容 |
|———-|——|
| bucket | State ファイルを保存する S3 バケット名 |
| key | バケット内のパス(State ファイルのキー) |
| region | S3 バケットのリージョン |
| dynamodb_table | ロック管理に使用する DynamoDB テーブル名 |
| encrypt | S3 サーバーサイド暗号化を有効化 |
terraform init -migrate-state
バックエンドを変更した際は、terraform init で移行が必要です。
terraform init -migrate-state
Initializing the backend...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "s3" backend. No existing state was found in the newly
configured "s3" backend. Do you want to copy this existing state to the new
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value:
yes と入力します。
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the lock file
- Using previously-installed hashicorp/aws v5.94.1
Terraform has been successfully initialized!
リモート State の確認
S3 コンソールで State ファイルが保存されているか確認します。
- S3 →
terraform-state-YOURNAMEバケットを開く handson/terraform.tfstateが存在することを確認
バージョニングが有効なので、以前のバージョンも保存されています。過去の State に戻すことも可能です。
DynamoDB ロックの動作確認
別のターミナルを開き、同時に terraform apply を実行してみます。
ターミナル 1:
cd ~/terraform-handson/step-e
terraform apply
ターミナル 2(ターミナル 1 が実行中の間に):
cd ~/terraform-handson/step-e
terraform apply
ターミナル 2 では以下のエラーが表示されます。
Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│
│ Error message: ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
│Path:terraform-state-YOURNAME/handson/terraform.tfstate
│Operation: OperationTypeApply
│Who: user@hostname
│Version:1.14.8
│Created:2026-04-13 13:30:00.000000000 +0000 UTC
│Info:
╵
DynamoDB によるロックが正常に機能していることが確認できます。チームでの同時実行を防ぐ重要な機能です。
Step E のクリーンアップ
ハンズオンが完了したら、作成したリソースを削除します。
# Step E のリソース削除
cd ~/terraform-handson/step-e
terraform destroy
# State 管理用リソースの削除
cd ~/terraform-handson/state-backend
# S3 バケットを空にする
aws s3 rm s3://terraform-state-YOURNAME --recursive
terraform destroy
注意: S3 バケットを削除するには、先にバケット内のオブジェクトをすべて削除する必要があります。
Section 3 まとめ
| Step | 学んだ内容 | 使用コマンド |
|---|---|---|
| A | 基本ワークフロー、S3バケット作成 | init / plan / apply / destroy |
| B | 複数リソース、暗黙的依存関係 | plan(4 to add の確認) |
| C | 変数化、dev/prod 環境分離 | -var-file による切り替え |
| D | Output 定義、値の参照 | output / output -raw |
| E | リモートState、S3+DynamoDB連携 | init -migrate-state |
Terraform の基本ワークフローから始まり、変数化・Output・リモート State まで、実務で必要な基礎スキルを一通り習得できました。
コスト管理: ハンズオン完了後は必ず
terraform destroyを実行してリソースを削除してください。
特に EC2 インスタンスは起動しているだけで課金が発生します。
Section 4: 実践Tips — チームでTerraformを使いこなす
ここまでで、Terraformの基本的な使い方(init / plan / apply / destroy)を学びました。このセクションでは、実務でTerraformを使う際に「知っておかないとハマる」実践的なノウハウをまとめます。
4-1. .gitignore の設定
TerraformプロジェクトをGitで管理する際、コミットすべきでないファイルとコミットすべきファイルを正しく区別することが重要です。以下の .gitignore テンプレートを使ってください。
# Terraformが自動生成するディレクトリ(プロバイダのバイナリなどが入る)
.terraform/
# ロックファイル:チームで共有するためコミットする(後述)
# .terraform.lock.hcl ← コメントアウト = コミット対象にする
# stateファイル(AWSリソースの現在の状態を記録する重要ファイル)
# ローカルに置く場合もコミット禁止(機密情報・差分衝突の原因)
terraform.tfstate
terraform.tfstate.backup
# 変数ファイル(アクセスキー・パスワードなどの機密情報を含む可能性)
*.tfvars
# サンプル変数ファイルはコミット可(値は dummy でよい)
!example.tfvars
# 手動オーバーライド用ファイル(通常は不要)
override.tf
override.tf.json
# Terraformがクラッシュしたときに生成されるログ
crash.log
各エントリの説明
| エントリ | 理由 |
|---|---|
.terraform/ | プロバイダのバイナリ(数十MB)が入る。terraform init で再生成できるため不要 |
terraform.tfstate | AWSリソースIDやパスワードなどの機密情報を含む可能性。S3などのリモートバックエンドで管理するのがベストプラクティス |
terraform.tfstate.backup | stateの前バージョン。同上の理由でコミット禁止 |
*.tfvars | TF_VAR_ で渡す機密値を記載することが多いため除外。terraform.tfvars も対象 |
!example.tfvars | ダミー値を入れたサンプルファイルは除外対象から外してコミット可能にする |
crash.log | クラッシュ時のスタックトレース。個人環境固有の情報が含まれる |
⚠️ .terraform.lock.hcl はコミットする
.terraform.lock.hcl は コミット対象 です。このファイルには、使用するプロバイダのバージョンとチェックサムが記録されています。
# .terraform.lock.hcl(例)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.40.0"
constraints = "~> 5.0"
hashes = [
"h1:...",
"zh:...",
]
}
このファイルをコミットすることで:
– チームメンバー全員が 同じバージョンのプロバイダ を使える
– CI/CD環境でも同じプロバイダが使われる
– 意図しないプロバイダのアップグレードを防げる
4-2. 日常ワークフロー
実務でTerraformを使う際の代表的なコマンドとその使いどころを紹介します。
terraform fmt — フォーマット統一
HCLファイルを公式スタイルに自動整形します。CIパイプラインに必須 です。
# カレントディレクトリ以下を再帰的にフォーマット
$ terraform fmt -recursive
# フォーマットが必要なファイルを確認だけ(CI用)
$ terraform fmt -check -recursive
# フォーマット対象があった場合の出力
main.tf
variables.tf
-check フラグを使うとフォーマットが崩れているとき終了コード1を返すため、CIでのゲートとして使えます。
terraform validate — 構文チェック
terraform plan の前に必ず実行します。構文エラーや不正な参照を検出します。
$ terraform validate
# 成功時
Success! The configuration is valid.
# エラー時
│ Error: Reference to undeclared resource
│on main.tf line 15, in resource "aws_s3_bucket" "example":
│15:bucket = aws_s3_bucket.typo.bucket
terraform plan — 差分確認(最重要コマンド)
apply 前に必ず実行し、変更内容をレビューします。チームでのコードレビュー時も plan の出力を共有するのがベストプラクティスです。
$ terraform plan
Terraform will perform the following actions:
# aws_s3_bucket.example will be created
+ resource "aws_s3_bucket" "example" {
+ bucket = "my-terraform-bucket-12345"
+ id = (known after apply)
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
出力の読み方:
– + : 新規作成
– ~ : 変更(in-place update)
– -/+ : 削除して再作成(ダウンタイムが発生する可能性)
– - : 削除
terraform plan -out=tfplan → terraform apply tfplan — 安全な適用手順
plan の結果を保存して、その結果だけを apply する手順です。本番環境では必ずこの手順を使ってください。
# ステップ1: planの結果をファイルに保存
$ terraform plan -out=tfplan
# ステップ2: 保存したplanを確認(人間が読める形式で表示)
$ terraform show tfplan
# ステップ3: 保存したplanのみをapply(planと apply の間に差分が生じない)
$ terraform apply tfplan
この手順のメリット:
– plan 実行後にリソースが変更されても、保存されたplan内容だけ が適用される
– レビュー済みのplanファイルをCIに渡してapplyするパターンにも使える
terraform show — 現在のstateを確認
$ terraform show
# aws_s3_bucket.example:
resource "aws_s3_bucket" "example" {
bucket = "my-terraform-bucket-12345"
id = "my-terraform-bucket-12345"
region = "ap-northeast-1"
...
}
terraform state list — 管理中リソース一覧
Terraformが管理しているリソースの一覧を確認します。
$ terraform state list
aws_s3_bucket.example
aws_s3_bucket_versioning.example
aws_iam_role.lambda_exec
aws_lambda_function.my_function
4-3. terraform.lock.hcl の役割
プロバイダバージョンを固定する仕組み
terraform init を実行すると、使用するプロバイダのバージョンと各プラットフォーム用のチェックサムが .terraform.lock.hcl に記録されます。
# 初回init時にロックファイルが生成される
$ terraform init
# 以降はロックファイルに記録されたバージョンが使われる
$ terraform init
# → "Terraform has been successfully initialized!"
# → プロバイダのダウンロードはスキップ(ロック済みのため)
チーム全員が同じプロバイダを使う重要性
プロバイダのバージョンが異なると、同じTerraformコードでも 動作が変わる ことがあります。
チームAのPC: aws provider 5.38.0 → plan結果: 変更なし
チームBのPC: aws provider 5.40.0 → plan結果: 差分あり(属性のデフォルト値が変わった)
.terraform.lock.hcl をコミットしておけば、terraform init 時に全員が同じバージョンをダウンロードします。
terraform init -upgrade でのプロバイダ更新
プロバイダを意図的に更新する場合は -upgrade フラグを使います。
# ロックファイルを無視して最新のプロバイダを取得 + ロックファイルを更新
$ terraform init -upgrade
# 更新後は必ずplanで差分確認
$ terraform plan
更新後は必ずチームでレビューし、問題がなければ更新された .terraform.lock.hcl をコミットします。
4-4. モジュール化の基礎概念
モジュールとは
モジュールとは、再利用可能な .tf ファイルのまとまり です。同じ構成(例:S3バケット + バージョニング + ライフサイクル)を複数の環境(dev/prod)で使い回す際に威力を発揮します。
ディレクトリ構造例
my-infra/
├── modules/# 再利用可能なモジュール置き場
│└── s3-bucket/
│ ├── main.tf # S3バケットのリソース定義
│ ├── variables.tf# モジュールへの入力変数
│ └── outputs.tf # モジュールからの出力値
└── environments/ # 環境ごとの設定
├── dev/
│├── main.tf # モジュールを呼び出す
│└── terraform.tfvars
└── prod/
├── main.tf # 同じモジュールを呼び出す(値が異なる)
└── terraform.tfvars
# environments/dev/main.tf
module "s3" {
source= "../../modules/s3-bucket"
bucket_name = "my-app-dev-bucket"
environment = "dev"
}
# environments/prod/main.tf
module "s3" {
source= "../../modules/s3-bucket"
bucket_name = "my-app-prod-bucket"
environment = "prod"
}
モジュール化のメリット:
– DRY原則(Don’t Repeat Yourself)を実現
– バグ修正をモジュール側で行えば全環境に反映
– チームでの役割分担(モジュール開発者 / 利用者)が明確になる
公開モジュール(Terraform Registry)
Terraform Registry では、コミュニティや各クラウドベンダーが公開しているモジュールを利用できます。
# AWS VPCモジュール(公開モジュールの使用例)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.2"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
本番利用の際は version を必ず固定してください。
4-5. CI/CD との統合
GitHub Actions での自動plan
プルリクエスト時に terraform plan を自動実行し、結果をPRコメントに投稿する構成が広く使われています。
# .github/workflows/terraform.yml(簡略版)
name: Terraform Plan
on:
pull_request:
paths:
- "**.tf"
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.5"
- name: Terraform Init
run: terraform init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Plan
run: terraform plan -no-color
このワークフローにより:
– コードレビュー時に plan 結果を自動確認できる
– terraform fmt -check を追加すればフォーマット漏れも検出できる
主要なTerraform自動化ツール
| ツール | 特徴 |
|---|---|
| Atlantis | GitHubのPRコメントでplan/applyを操作。セルフホスト型 |
| Terraform Cloud | HashiCorpが提供するSaaS型。stateのリモート管理も含む |
| Spacelift | より高度なポリシーやドリフト検出が可能なSaaS型 |
小規模なチームにはAtlantis、エンタープライズ要件があればTerraform CloudやSpaceliftが適しています。
4-6. Step Functionsシリーズへの接続
本記事で学んだTerraformの基礎知識は、AWSのStep Functionsを実践的に構築する記事の前提知識として活用できます。以下のシリーズ記事では、各セクションにTerraformによる実装ハンズオンが含まれています。
関連記事
AWS Step Functions 入門
Step Functionsの基本概念からTerraformでのステートマシン構築まで。aws_sfn_state_machineリソースや ASL(Amazon States Language)の記述方法を解説しています。ECS × Step Functions 入門
ECSタスクをStep Functionsから起動するアーキテクチャのTerraform実装。aws_ecs_task_definition、aws_iam_roleなどを組み合わせた実践的な構成を学べます。
これらの記事のTerraformセクションでは、本記事で紹介した以下の知識が前提となっています:
– terraform init / plan / apply の基本操作
– 変数(variables.tf)と出力(outputs.tf)の使い方
– .gitignore によるstateファイルの管理
– プロバイダのバージョン固定(.terraform.lock.hcl)
Section 4 まとめ
| Tips | ポイント |
|---|---|
.gitignore | stateとtfvarsは除外。ロックファイルはコミット |
| 日常ワークフロー | fmt → validate → plan → apply の順番を守る |
| 安全なapply | -out=tfplan で計画を保存してから適用 |
| ロックファイル | チームの一致性のためコミット必須 |
| モジュール化 | 環境をまたぐ共通構成はモジュールに切り出す |
| CI/CD | PRごとに自動planを実行してレビューに組み込む |
Section 5: ハンズオン後の削除手順
5-1. terraform destroy の実行
ハンズオン完了後は terraform destroy でリソースを削除してください。EC2インスタンスは起動中でも課金が継続するため、不要になったら速やかに削除することを推奨します。
削除前に plan で確認
# 削除対象を事前に確認(dry-run)
terraform plan -destroy
plan -destroy を実行すると、削除されるリソースの一覧が表示されます。実際には何も削除されないので、安心して確認できます。
Plan: 0 to add, 0 to change, 4 to destroy.
# aws_instance.handson_ec2 will be destroyed
# aws_key_pair.handson will be destroyed
# aws_s3_bucket.handson will be destroyed
# aws_security_group.handson_sg will be destroyed
削除の実行
terraform destroy
確認プロンプトが表示されるので yes を入力してください。
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
💡 削除順序はTerraformが自動解決します。
aws_instance→aws_security_groupのように依存関係を考慮した順序で削除されるため、手動で順序を意識する必要はありません。
5-2. State ファイルの後処理
ローカル State(Step A〜D の場合)
Step A〜D ではローカルの terraform.tfstate にインフラ状態が記録されています。terraform destroy 完了後は、このファイルをプロジェクトから除外してください。
# destroy 後に残っているStateファイルを確認
ls -la terraform.tfstate terraform.tfstate.backup
# git 管理外であることを確認(.gitignore に記載されているはず)
cat .gitignore | grep tfstate
⚠️ terraform.tfstate は機密情報を含む場合があります。 リソースのIDやARNなどが平文で記録されているため、誤って git にコミットしないよう
.gitignoreへの記載を必ず確認してください。
リモート State(Step E の場合)
Step E でリモート State を設定した場合、S3 バケットと DynamoDB テーブルの削除には追加手順が必要です。これらのリソースは Terraform の backend 設定そのものに使われているため、terraform destroy だけでは削除されません。
# Step 1: backend を local に戻す(main.tf の backend "s3" ブロックをコメントアウト)
# backend "s3" { ... } をコメントアウトまたは削除した後:
terraform init -migrate-state
# プロンプト: "Do you want to copy existing state to the new backend?" → yes
# Step 2: State 管理リソースを削除
terraform destroy
# S3バケット(terraform-state-YOURNAME)と DynamoDBテーブルが削除対象になる
5-3. 削除時の注意事項
S3 バケットにオブジェクトが残っている場合
S3 バケットにオブジェクト(ファイル)が存在すると、terraform destroy が失敗します。事前にバケットを空にしてください。
# バケット内のオブジェクトをすべて削除
aws s3 rm s3://terraform-handson-bucket-YOURNAME --recursive
# 削除確認
aws s3 ls s3://terraform-handson-bucket-YOURNAME
# (何も表示されなければOK)
削除後の残存リソース確認
terraform destroy が完了したら、State にリソースが残っていないことを確認します。
# Stateに残っているリソースを確認(空であればOK)
terraform state list
No state file was found! または空の出力が返れば、すべて削除されています。
EC2 インスタンスの「停止」と「削除」の違い
| 操作 | コスト | 永続性 |
|---|---|---|
| 停止(Stop) | EBSストレージ料金は継続発生 | データ保持 |
| 削除(Terminate) | 課金停止 | データ消去 |
terraform destroy は削除(Terminate)を実行します。ハンズオンのEC2は削除で問題ありませんが、本番環境では誤削除に注意してください。
5-4. 部分削除(特定リソースのみ)
特定のリソースだけを削除したい場合は -target オプションを使います。
# EC2インスタンスのみ削除
terraform destroy -target=aws_instance.handson_ec2
# Security Group のみ削除
terraform destroy -target=aws_security_group.handson_sg
⚠️
-targetは緊急時の手段です。 依存関係のあるリソースを個別に削除すると State と実際のインフラが不整合になる場合があります。通常のクリーンアップは-targetを使わずterraform destroyを実行してください。
Section 6: まとめ
6-1. 学習内容の振り返り
本記事では Terraform の基礎から実践まで、段階的なハンズオンを通して習得しました。以下のスキルチェックリストで習得内容を確認してください。
インストール・セットアップ
– [ ] Terraform CLI をインストールし、terraform version で動作確認できる
– [ ] AWS CLI を設定し、aws sts get-caller-identity で認証確認できる
– [ ] .gitignore に *.tfstate, *.tfvars, .terraform/ を追加できる
Step A: S3バケット最小構成
– [ ] terraform {} ブロックで required_version と required_providers を設定できる
– [ ] provider "aws" でリージョンを指定できる
– [ ] resource "aws_s3_bucket" でS3バケットをコードで作成できる
– [ ] terraform init → plan → apply のワークフローを実行できる
Step B: EC2インスタンス追加
– [ ] Security Group リソースで inbound/outbound ルールを定義できる
– [ ] aws_key_pair でSSHキーペアを登録できる
– [ ] aws_instance で EC2 インスタンスをコードで起動できる
– [ ] リソース間の参照(aws_security_group.handson_sg.id)を理解している
Step C: 変数化
– [ ] variable ブロックで type / default / description を設定できる
– [ ] terraform.tfvars で変数値を環境ごとに分離できる
– [ ] var.xxx で変数を参照できる
Step D: Output
– [ ] output ブロックで作成したリソースの情報(IP・ARN等)を出力できる
– [ ] terraform output コマンドで値を確認できる
Step E: リモート State
– [ ] S3 バケットと DynamoDB テーブルで State 管理基盤を構築できる
– [ ] backend "s3" ブロックでリモート State を設定できる
– [ ] terraform init -migrate-state でローカル State をリモートへ移行できる
– [ ] State ロックにより複数人同時実行が防止されることを理解している
削除・クリーンアップ
– [ ] terraform plan -destroy で削除対象を事前確認できる
– [ ] terraform destroy でリソースを安全に削除できる
– [ ] S3バケット内オブジェクトの事前削除など、destroy の前提条件を把握している
6-2. 本記事が前提知識となる Step Functions シリーズ
Terraform の基礎を習得したら、以下の Step Functions シリーズでサーバーレスワークフロー開発にステップアップしてください。すべての記事でコンソール操作と Terraform の両方を解説しています。
| # | 記事 | 習得スキル |
|---|---|---|
| 第1回 | AWS Step Functions 入門 — コンソールとTerraformで学ぶハンズオン | ステートマシン基礎・Lambda連携 |
| 第2回 | ECS × Step Functions 入門 — CSVバッチをFargateタスクでジョブ化するハンズオン | Fargateバッチ・.sync統合 |
| 第3回 | Step Functions エラーハンドリング完全ガイド | Retry/Catch・冪等性設計 |
| 第4回 | Step Functions 入出力データフロー制御完全ガイド | InputPath/OutputPath/ResultPath |
| 第5回 | Step Functions Callbackパターン完全ガイド | waitForTaskToken・非同期処理 |
| 第6回 | Step Functions Distributed Map完全ガイド | 大規模並列処理・S3データ処理 |
| 第7回 | Step Functions Express vs Standard完全ガイド | ワークフロータイプ選択基準 |
| 第8回 | Step Functions SDK Direct Integration完全ガイド | Lambda不要のSDK直接統合 |
6-3. 次のステップ
本記事で Terraform の基礎を押さえたら、以下のトピックで実践力をさらに高めましょう。
モジュール化(DRY な構成管理)
# モジュールを使うと環境間でコードを再利用できる
module "s3_bucket" {
source= "./modules/s3"
bucket_name = var.bucket_name
environment = var.environment
}
同じリソースを dev/stg/prod で使い回す場合にモジュールが威力を発揮します。
Terragrunt による DRY 構成
Terragrunt は Terraform のラッパーツールで、複数環境・複数リージョンの構成を DRY(Don’t Repeat Yourself)に管理できます。大規模なマルチアカウント構成に特に有効です。
GitHub Actions による CI/CD
# .github/workflows/terraform.yml(例)
- name: Terraform Plan
run: terraform plan -no-color
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
Pull Request 時に terraform plan を自動実行し、マージ時に terraform apply を実行する CI/CD パイプラインを構築できます。
AWS CDK / Pulumi との比較
| ツール | 言語 | 学習コスト | 特徴 |
|---|---|---|---|
| Terraform | HCL | 低〜中 | マルチクラウド・エコシステムが豊富 |
| AWS CDK | TypeScript / Python 等 | 中〜高 | AWSネイティブ・型安全 |
| Pulumi | TypeScript / Python 等 | 中〜高 | マルチクラウド・汎用言語で記述 |
AWS 専用ならCDKも強力な選択肢ですが、マルチクラウドや既存の Terraform エコシステムを活用したい場合は Terraform が定番です。
6-4. 参考リンク
- Terraform 公式ドキュメント(registry.terraform.io)
- Terraform AWS Provider ドキュメント
- HashiCorp Learn — Terraform Get Started(AWS)
- terraform.lock.hcl の仕組み(公式)
6-5. 記事フッター
本記事は「AWS ハンズオン TechBlog」Terraform シリーズの第1回です。
次回以降は Step Functions シリーズと合わせて、Terraform で AWS サーバーレス基盤を構築する実践的な内容を公開予定です。