DockerでTerraformの実行環境を構築する方法

今まで逃げてきたTerraformを使ったインフラ構築を行うため、Dockerを使ってTerraformの実行環境を作ったので備忘録として残しておく。

マジでインフラの領域の知識がなさすぎて、解説とかは全然できないが、とりあえずS3バケットを作成できただけでも進歩。

環境情報

・macOS Sequoia

・Docker

・VSCode

前提

Dockerがすでに導入されており、docker-compose.ymlの記述がある程度理解でき、Dockerを触ったことがある方を前提としています。

Lambda作成の権限がついているAWSユーザのアクセスキーIDとシークレットキーを控えていること。(環境変数に設定する際に使用)

AWSユーザの作成がわからない人はググるかAIに聞いてくれればわかりやすい手順がたくさん出てきます。

以下のプロジェクトを例で手順を書いていきます。

GitHub - aitea1/allTest at docker-terraform

プロジェクト構成

.
├── docker-compose.yml
└── terraform
    ├── environments
    │   └── dev
    │       ├── dev.tfvars
    │       ├── main.tf
    │       ├── outputs.tf
    │       ├── provider.tf
    │       ├── terraform.tfstate
    │       └── variables.tf
    ├── lambda
    │   └── index.js
    └── modules
        ├── compute
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        ├── data
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        ├── lambda
        │   ├── lambda_function_dev.zip
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        └── network
            ├── main.tf
            ├── outputs.tf
            └── variables.tf

手順1

.envファイルを作成し、環境変数を設定

AWS_ACCESS_KEY_ID=[AWSユーザ作成した際のアクセスキー]
AWS_SECRET_ACCESS_KEY=[AWSユーザ作成した際のシークレットアクセスキー]

手順2

docker-composeファイルの作成

version: "3.8"
services:
  terraform-trial:
    image: hashicorp/terraform:1.7.5
    environment:
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_REGION=${AWS_REGION:-ap-northeast-1}
    volumes:
      - ./trial/terraform:/terraform
    working_dir: /terraform
    entrypoint: ash
    tty: true

手順3
docker-compose up -dコマンド実施、実施後コンテナにアタッチ。

手順3まで実施できれば最低限テラフォームを書く環境は整った。

docker-compose up -d

手順4

ディレクトリ作成

ディレクトリの構成はなんか前にツイッターで見かけた構成が綺麗だったからそれを参考に作成

mkdir -p ./environments/dev ./modules/compute ./modules/data ./modules/network

手順5

tfファイルを作成

dev.tfvarsのbucket_name_prefixがバケット名になります。

バケット名の後ろにmain.tfの「resource “random_id” “suffix” {」で定義されているランダムな文字列がくっつきます。

/environments/dev/

# 開発環境の具体的な設定値を定義するファイル
# terraform apply -var-file=dev.tfvars コマンドで使用される

region        = "ap-northeast-1"  # デプロイするAWSリージョン(東京)
environment   = "dev"             # 開発環境を示す識別子

# S3バケット設定
bucket_name_prefix = "trial-s3Bucket"  # アプリケーションデータを示すバケット名
enable_versioning = true            # データの変更履歴を保持する機能を有効化
enable_lifecycle_rules = true       # コスト最適化のためのデータ自動移行・削除ポリシーを有効化
expiration_days = 1095              # データを3年間保持し、その後自動削除(コンプライアンス要件)
# 開発環境のインフラリソース定義ファイル
# 必要なモジュールを呼び出してdev環境のリソースを作成

# S3バケットモジュールを追加
# 開発環境用のデータ保存バケットをセットアップ
module "data" {
  source = "../../modules/data"
  
  environment        = var.environment       # 「dev」環境として設定
  bucket_name_prefix = var.bucket_name_prefix  # バケット名のプレフィックス設定
  enable_versioning  = var.enable_versioning   # バージョニング機能の有効/無効を設定
  enable_lifecycle_rules = var.enable_lifecycle_rules  # ライフサイクルポリシーの有効/無効を設定
  expiration_days    = var.expiration_days     # データ保持期間を設定
}

# 他のモジュールも必要に応じて追加
# 例: コンピューティングリソース、データベース、ネットワークなど
# 開発環境の出力値定義ファイル
# 作成したリソースの重要な情報を出力し、他のシステムやユーザーが参照できるようにする

# 既存の出力...

# S3バケット出力
# データストレージモジュールから作成されたバケットの情報を出力
output "s3_bucket_id" {
  description = "作成されたS3バケットのID"
  value       = module.data.bucket_id
  # バケットを参照するための基本的な識別子(バケット名)
}

output "s3_bucket_arn" {
  description = "S3バケットのARN"
  value       = module.data.bucket_arn
  # IAMポリシーやリソースポリシーで使用するための完全修飾リソース名
}

output "s3_bucket_domain_name" {
  description = "S3バケットのドメイン名"
  value       = module.data.bucket_domain_name
  # バケットへのアクセスURLを構築するためのドメイン名
}
# AWSプロバイダと必要なバージョン設定のためのファイル
# Terraformがどのプロバイダ(AWS)を使用し、どのように認証するかを定義

provider "aws" {
  region = var.region  # 環境変数ファイルで指定されたリージョンを使用
}

terraform {
  # 必要なプロバイダとバージョン制約の定義
  # プロジェクトの安定性のため特定のバージョン範囲に制限
  required_providers {
    aws = {
      source  = "hashicorp/aws"  # AWSリソースを管理するためのプロバイダ
      version = "~> 4.0"         # 4.xの最新バージョンを使用
    }
    archive = {
      source  = "hashicorp/archive"  # ZIPファイルなどのアーカイブを作成するためのプロバイダ
      version = "~> 2.0"             # 2.xの最新バージョンを使用
    }
  }
  
  # 必要に応じてバックエンドの設定を追加
  # Terraformの状態ファイルをS3に保存するための設定(現在はコメントアウト)
  # backend "s3" {
  #   bucket = "my-terraform-state"
  #   key    = "dev/terraform.tfstate"
  #   region = "ap-northeast-1"
  # }
}
variable "region" {
  description = "AWSのリージョン"
  type        = string
}

variable "environment" {
  description = "環境名"
  type        = string
  default     = "dev"
}

# S3バケット関連の変数
variable "bucket_name_prefix" {
  description = "S3バケット名のプレフィックス"
  type        = string
  # バケットの用途を示す名前の接頭辞
}

variable "enable_versioning" {
  description = "バケットのバージョニングを有効にするかどうか"
  type        = bool
  default     = true
  # データ保全のためのバージョン管理機能
}

variable "enable_lifecycle_rules" {
  description = "ライフサイクルルールを有効にするかどうか"
  type        = bool
  default     = false
  # ストレージコスト最適化のための自動データ管理ポリシー
}

variable "expiration_days" {
  description = "オブジェクトの有効期限(日数)"
  type        = number
  default     = 730  # 2年
  # コンプライアンスやコスト管理のためのデータ保持期間設定
}

/modules/data/

# S3バケットモジュール
# このモジュールは環境ごとのデータ保存用のS3バケットとその設定を管理します

# S3バケットの作成
# アプリケーションデータを保存するためのメインストレージとして機能
resource "aws_s3_bucket" "data_bucket" {
  bucket = "${var.bucket_name_prefix}-${var.environment}-${random_id.suffix.hex}"
  
  tags = {
    Name        = "${var.bucket_name_prefix}-${var.environment}"
    Environment = var.environment
  }
}

# ランダムなサフィックスを生成してバケット名の一意性を確保
# S3バケット名はAWS全体で一意である必要があるため、衝突を防ぐためのランダム文字列を生成
resource "random_id" "suffix" {
  byte_length = 4
}

# バケットのパブリックアクセスをブロック
# セキュリティのため、意図しないパブリックアクセスを防止する設定
resource "aws_s3_bucket_public_access_block" "data_bucket_access" {
  bucket = aws_s3_bucket.data_bucket.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# バケットのバージョニング設定
# データ保護のため、オブジェクトの以前のバージョンを保持する機能
# 誤削除や上書きからの復旧が可能になります
resource "aws_s3_bucket_versioning" "data_bucket_versioning" {
  bucket = aws_s3_bucket.data_bucket.id
  
  versioning_configuration {
    status = var.enable_versioning ? "Enabled" : "Disabled"
  }
}

# ライフサイクルルールの設定(オプション)
# ストレージコスト最適化のための自動データ管理ポリシー
# 時間経過とともにデータをより低コストなストレージクラスに移行し、最終的に削除します
resource "aws_s3_bucket_lifecycle_configuration" "data_bucket_lifecycle" {
  bucket = aws_s3_bucket.data_bucket.id

  rule {
    id     = "archive-and-delete"
    status = var.enable_lifecycle_rules ? "Enabled" : "Disabled"

    transition {
      days          = 90
      storage_class = "STANDARD_IA"  # 90日後にアクセス頻度の低いストレージに移行
    }

    transition {
      days          = 365
      storage_class = "GLACIER"  # 1年後にさらに低コストの長期保存用ストレージに移行
    }

    expiration {
      days = var.expiration_days  # 設定した日数後にオブジェクトを完全に削除
    }
  }
}
# データストレージモジュールの出力値定義ファイル
# 作成したS3バケットの情報を他のリソースやモジュールで参照できるようにするための出力値

output "bucket_id" {
  description = "作成されたS3バケットのID"
  value       = aws_s3_bucket.data_bucket.id
  # バケットを参照するために使用する基本的な識別子
}

output "bucket_arn" {
  description = "S3バケットのARN"
  value       = aws_s3_bucket.data_bucket.arn
  # IAMポリシーやリソースポリシーで参照するためのAmazon Resource Name
}

output "bucket_domain_name" {
  description = "S3バケットのドメイン名"
  value       = aws_s3_bucket.data_bucket.bucket_domain_name
  # バケットにHTTPリクエストを送信する際のエンドポイントURLを構築するために使用
}

output "bucket_regional_domain_name" {
  description = "S3バケットのリージョナルドメイン名"
  value       = aws_s3_bucket.data_bucket.bucket_regional_domain_name
  # 特定リージョンに最適化されたエンドポイントURLを構築するために使用
}
# データストレージモジュールの変数定義ファイル
# S3バケットの設定を外部から制御するためのパラメータを定義

variable "environment" {
  description = "デプロイする環境(dev、staging、prod)"
  type        = string
  # 環境ごとに異なるバケットを作成するための識別子
}

variable "bucket_name_prefix" {
  description = "S3バケット名のプレフィックス"
  type        = string
  # バケットの用途を示す名前の接頭辞。最終的なバケット名は prefix-env-randomsuffix 形式になる
}

variable "enable_versioning" {
  description = "バケットのバージョニングを有効にするかどうか"
  type        = bool
  default     = true
  # データの変更履歴を保持する機能。誤削除や上書きからの復旧に役立つ
}

variable "enable_lifecycle_rules" {
  description = "ライフサイクルルールを有効にするかどうか"
  type        = bool
  default     = false
  # ストレージコスト最適化のための自動データ移行・削除ポリシーを有効化する設定
}

variable "expiration_days" {
  description = "オブジェクトの有効期限(日数)"
  type        = number
  default     = 730  # 2年
  # データ保持期間を設定。コンプライアンス要件やコスト管理に関連
}

手順6

terraform initコマンド実行

cd environments/dev
terraform init

実行後、「Terraform has been successfully initialized!」が出ればOK!

手順7

terraform planコマンド実施

terraform plan -var-file=dev.tfvars

実行結果

S3バケット関連が「+」になっていればOK

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:

  # module.data.aws_s3_bucket.data_bucket will be created
  + resource "aws_s3_bucket" "data_bucket" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = (known after apply)
      + 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)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Environment" = "dev"
          + "Name"        = "trial-S3bucket-dev"
        }
      + tags_all                    = {
          + "Environment" = "dev"
          + "Name"        = "trial-S3bucket-dev"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }

  # module.data.aws_s3_bucket_lifecycle_configuration.data_bucket_lifecycle will be created
  + resource "aws_s3_bucket_lifecycle_configuration" "data_bucket_lifecycle" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + rule {
          + id     = "archive-and-delete"
          + status = "Enabled"

          + expiration {
              + days                         = 1095
              + expired_object_delete_marker = (known after apply)
            }

          + transition {
              + days          = 365
              + storage_class = "GLACIER"
            }
          + transition {
              + days          = 90
              + storage_class = "STANDARD_IA"
            }
        }
    }

  # module.data.aws_s3_bucket_public_access_block.data_bucket_access will be created
  + resource "aws_s3_bucket_public_access_block" "data_bucket_access" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

  # module.data.aws_s3_bucket_versioning.data_bucket_versioning will be created
  + resource "aws_s3_bucket_versioning" "data_bucket_versioning" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

  # module.data.random_id.suffix will be created
  + resource "random_id" "suffix" {
      + b64_std     = (known after apply)
      + b64_url     = (known after apply)
      + byte_length = 4
      + dec         = (known after apply)
      + hex         = (known after apply)
      + id          = (known after apply)
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + s3_bucket_arn         = (known after apply)
  + s3_bucket_domain_name = (known after apply)
  + s3_bucket_id          = (known after apply)

─────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform
can't guarantee to take exactly these actions if you run "terraform
apply" now.

手順8

terraform applyコマンド実施

terraform apply -var-file=dev.tfvars

「Do you want to perform these actions?
Terraform will perform the actions described above.
Only ‘yes’ will be accepted to approve.」

と聞かれるので「yes」を入力し、「Apply complete!」が表示されればS3バケット作成完了。

手順9

作成されているかの確認

マネジメントコンソールからS3バケットを確認し、バケットが作成されていればOK

AWS コンソールの S3 バケット一覧に表示された、Terraform で作成した trial-s3bucket-dev バケット

おまけ

作成したS3バケットを消したい時はenvironments/dev配下のtfファイルからS3バケットに関する記載を消すなりコメントアウトすればOK

/environments/dev

region        = "ap-northeast-1"  # デプロイするAWSリージョン(東京)
# AWSプロバイダと必要なバージョン設定のためのファイル
# Terraformがどのプロバイダ(AWS)を使用し、どのように認証するかを定義

provider "aws" {
  region = var.region  # 環境変数ファイルで指定されたリージョンを使用
}

terraform {
  # 必要なプロバイダとバージョン制約の定義
  # プロジェクトの安定性のため特定のバージョン範囲に制限
  required_providers {
    aws = {
      source  = "hashicorp/aws"  # AWSリソースを管理するためのプロバイダ
      version = "~> 4.0"         # 4.xの最新バージョンを使用
    }
    archive = {
      source  = "hashicorp/archive"  # ZIPファイルなどのアーカイブを作成するためのプロバイダ
      version = "~> 2.0"             # 2.xの最新バージョンを使用
    }
  }
}
# 開発環境の変数定義ファイル
# dev環境に特化したパラメータを設定するための変数を定義

variable "region" {
  description = "AWSのリージョン"
  type        = string
  # リソースをデプロイするAWSのリージョン(地理的なデータセンターの場所)
}

terraform plan -var-file=dev.tfvarsを実施。

手順7で+になっていた部分が-になればOK

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:

  # module.data.aws_s3_bucket.data_bucket will be destroyed
  # (because aws_s3_bucket.data_bucket is not in configuration)
  - resource "aws_s3_bucket" "data_bucket" {
      - arn                         = "arn:aws:s3:::trial-s3bucket-dev-b8b4ed89" -> null
      - bucket                      = "trial-s3bucket-dev-b8b4ed89" -> null
      - bucket_domain_name          = "trial-s3bucket-dev-b8b4ed89.s3.amazonaws.com" -> null
      - bucket_regional_domain_name = "trial-s3bucket-dev-b8b4ed89.s3.ap-northeast-1.amazonaws.com" -> null
      - force_destroy               = false -> null
      - hosted_zone_id              = "Z2M4EHUR26P7ZW" -> null
      - id                          = "trial-s3bucket-dev-b8b4ed89" -> null
      - object_lock_enabled         = false -> null
      - region                      = "ap-northeast-1" -> null
      - request_payer               = "BucketOwner" -> null
      - tags                        = {
          - "Environment" = "dev"
          - "Name"        = "trial-s3bucket-dev"
        } -> null
      - tags_all                    = {
          - "Environment" = "dev"
          - "Name"        = "trial-s3bucket-dev"
        } -> null

      - grant {
          - id          = "9e7dece75611f03cd60f299df8085b17c2dbeba2b45d683c315bb7db6d20e33c" -> null
          - permissions = [
              - "FULL_CONTROL",
            ] -> null
          - type        = "CanonicalUser" -> null
        }

      - lifecycle_rule {
          - abort_incomplete_multipart_upload_days = 0 -> null
          - enabled                                = true -> null
          - id                                     = "archive-and-delete" -> null
          - tags                                   = {} -> null
          - expiration {
              - days                         = 1095 -> null
              - expired_object_delete_marker = false -> null
            }

          - transition {
              - days          = 365 -> null
              - storage_class = "GLACIER" -> null
            }
          - transition {
              - days          = 90 -> null
              - storage_class = "STANDARD_IA" -> null
            }
        }

      - server_side_encryption_configuration {
          - rule {
              - bucket_key_enabled = false -> null
              - apply_server_side_encryption_by_default {
                  - sse_algorithm = "AES256" -> null
                }
            }
        }

      - versioning {
          - enabled    = true -> null
          - mfa_delete = false -> null
        }
    }

  # module.data.aws_s3_bucket_lifecycle_configuration.data_bucket_lifecycle will be destroyed
  # (because aws_s3_bucket_lifecycle_configuration.data_bucket_lifecycle is not in configuration)
  - resource "aws_s3_bucket_lifecycle_configuration" "data_bucket_lifecycle" {
      - bucket = "trial-s3bucket-dev-b8b4ed89" -> null
      - id     = "trial-s3bucket-dev-b8b4ed89" -> null
      - rule {
          - id     = "archive-and-delete" -> null
          - status = "Enabled" -> null
          - expiration {
              - days                         = 1095 -> null
              - expired_object_delete_marker = false -> null
            }

          - filter {
            }

          - transition {
              - days          = 365 -> null
              - storage_class = "GLACIER" -> null
            }
          - transition {
              - days          = 90 -> null
              - storage_class = "STANDARD_IA" -> null
            }
        }
    }

  # module.data.aws_s3_bucket_public_access_block.data_bucket_access will be destroyed
  # (because aws_s3_bucket_public_access_block.data_bucket_access is not in configuration)
  - resource "aws_s3_bucket_public_access_block" "data_bucket_access" {
      - block_public_acls       = true -> null
      - block_public_policy     = true -> null
      - bucket                  = "trial-s3bucket-dev-b8b4ed89" -> null
      - id                      = "trial-s3bucket-dev-b8b4ed89" -> null
      - ignore_public_acls      = true -> null
      - restrict_public_buckets = true -> null
    }

  # module.data.aws_s3_bucket_versioning.data_bucket_versioning will be destroyed
  # (because aws_s3_bucket_versioning.data_bucket_versioning is not in configuration)
  - resource "aws_s3_bucket_versioning" "data_bucket_versioning" {
      - bucket = "trial-s3bucket-dev-b8b4ed89" -> null
      - id     = "trial-s3bucket-dev-b8b4ed89" -> null
      - versioning_configuration {
          - status = "Enabled" -> null
        }
    }

  # module.data.random_id.suffix will be destroyed
  # (because random_id.suffix is not in configuration)
  - resource "random_id" "suffix" {
      - b64_std     = "uLTtiQ==" -> null
      - b64_url     = "uLTtiQ" -> null
      - byte_length = 4 -> null
      - dec         = "3098865033" -> null
      - hex         = "b8b4ed89" -> null
      - id          = "uLTtiQ" -> null
    }

Plan: 0 to add, 0 to change, 5 to destroy.

Changes to Outputs:
  - s3_bucket_arn         = "arn:aws:s3:::trial-s3bucket-dev-b8b4ed89" -> null
  - s3_bucket_domain_name = "trial-s3bucket-dev-b8b4ed89.s3.amazonaws.com" -> null
  - s3_bucket_id          = "trial-s3bucket-dev-b8b4ed89" -> null

─────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform
can't guarantee to take exactly these actions if you run "terraform
apply" now.
/terraform/environments/dev #

terraform apply -var-file=dev.tfvarsを実施。

「Apply complete! Resources: 0 added, 0 changed, 5 destroyed.」が出ているのを確認後、S3バケットを確認し、対象のものが消えてればOK

今後はマネジメントコンソールはなるべく使わずにTerraformを使って環境構築していくように頑張る。

ほぼほぼAIに聞きながら環境構築したせいで参考サイトがほぼない。。。

参考サイト(ツイート)

Terraformを使用したAWS環境構築のディレクトリ構造
分かりやすくまとめられている pic.twitter.com/KWEYt83dxI

— 株式会社 協栄情報/CO-PROSPERITY INFORMATION Co.ltd (@cpi_co_jp) May 14, 2025