S3 + CloudFront を使った静的サイトホスティングを、Terraform を使って構築してみます。
全体像
- S3バケットを作り、静的サイトホスティング設定
- バケットに静的コンテンツ(HTML, CSS, JS など)を配置
- Websiteホスティングを有効化
- CloudFrontを利用してS3のコンテンツをCDN配信
- S3をオリジン(Origin)としたCloudFrontのDistributionを作成
- Terraformの構成管理
- コードでAWSリソースを定義
- Terraformでモジュール化し、再利用性を高める
今回はパブリックアクセスブロックを解除せずに、S3バケットを private のままでCloudFront 経由にのみ静的ファイルを配信する構成をTerraform を使って構築します。
この構成により、誤ってバケットをインターネットに全公開してしまうリスクを大幅に低減できます。
また、Terraformのモジュール化により、S3 と CloudFront のリソース定義を整理して再利用しやすい形にしています。
事前準備
- AWSアカウントの作成と初期設定(AWS CLI のインストールや IAM ユーザー作成など)
- Terraformのインストール(バージョンは記事執筆時点では v1.4.x など)
参考レポジトリ
こちらをgit pullして使います
ディレクトリ構成イメージ
moduleにして可読性を高めています。
.
├─ main.tf # 全体のエントリーポイント
├─ variables.tf # 変数定義
├─ outputs.tf # 出力(Outputs)定義
├─ modules
│ ├─ s3
│ │ ├─ main.tf
│ │ ├─ variables.tf
│ │ └─ outputs.tf
│ ├─ cloudfront
│ │ ├─ main.tf
│ │ ├─ variables.tf
│ │ └─ outputs.tf
│ └─ route53
│ ├─ main.tf
│ ├─ variables.tf
│ └─ outputs.tf
└─ ...
Terraform解説
トップレベルのファイル
main.tf
- module “s3_website”
バケット名や、index.html の名前などを設定
また、CloudFrontで使用する OAI の CanonicalUserID を取得するために module.cloudfront.oai_canonical_user_id
を参照しています。
- module “cloudfront”
S3バケットのリージョナルドメイン名をオリジンとして設定
CloudFrontはOAIを使ってS3へプライベートアクセスします。
variables.tf
今回は主要な変数として、S3バケット名・index.html・error.html を指定
これらにデフォルト値を入れてもOKですが、バケット名はユニークである必要があるため、実行時に .tfvars
などで指定するのが一般的です。
outputs.tf
terraform output
コマンドで確認できるようになっています。
cloudfront_domain_name
に出力される値(例: d111111abcdef8.cloudfront.net
) が、実際にブラウザでアクセスするURLになります。
S3モジュール
modules/s3
ディレクトリ配下に、バケット本体やウェブサイト設定、バケットポリシーを定義します。ここではバケットを privateに保ったまま、CloudFront OAIにのみ s3:GetObject
を許可します。
main.tf
S3 を完全に privateに設定したいため、aws_s3_bucket_acl
で public-read
などを指定せず、Block Public Access 設定を維持したままでもエラーになりません。
ウェブサイトホスティング設定 (aws_s3_bucket_website_configuration
) は、S3のウェブサイトエンドポイントを有効化しますが、直接アクセスは原則403になる場合が多いです。
- 実際の配信は CloudFront ドメイン経由
- これで静的サイトのトップページなどが
index.html
やerror.html
となることを指定
cloudfrontモジュール
modules/cloudfront
ディレクトリでは、OAI (Origin Access Identity) を作り、CloudFront Distributionと紐付けます。
main.tf
aws_cloudfront_origin_access_identity
- S3バケットへプライベートアクセスするためのIDを作成
aws_cloudfront_distribution
- S3をオリジンに設定 (
s3_domain_name
) origin_access_identity
に、上記のOAIを指定する
- S3をオリジンに設定 (
viewer_protocol_policy = "redirect-to-https"
- HTTP でアクセスしても HTTPS にリダイレクト
実行手順
terraform init
- 使用するプロバイダ(AWSなど)をダウンロード
terraform plan
- どのリソースが作成(変更)されるかを確認
- bucket名を入力してくださいというダイアログが出てくると思いますが、tfvarsファイルを作ってそこにbucket名を指定してあげると便利です

terraform apply
- 作成を実行し、数分待つ(CloudFrontの反映に数分~10分程度かかることも)
Terraformが完了すると、以下のように出力が得られます。
Apply complete! Resources: X added, 0 changed, 0 destroyed.
Outputs:
cloudfront_distribution_id = "EABCDEFG12345"
cloudfront_domain_name = "d1abc2xyz.cloudfront.net"
s3_bucket_name = "my-unique-private-bucket"
S3やcloudfrontのダッシュボードを見に行くと以下のようにしっかり作成されておりました。

動作確認
- S3バケットに index.html, error.html などをアップロード
- index.htmlがレポジトリにありますのでお使い下さい

- ブラウザで
https://<CloudFrontドメイン>/
にアクセス(terraformの実行時に出力されたcloudfront_domain_name )- 例:
https://d1abc2xyz.cloudfront.net
- 数分かかる場合がありますが、S3上の
index.html
が表示されれば成功です。(レポジトリのものを使っていれば以下画像のようになります。
- 例:

S3のウェブサイトエンドポイントに直接アクセスしても、403エラーになる(あるいはブロックされる)はずです。これはバケットが完全privateで、パブリックアクセスがブロックされている証拠です。

後片付け
動作確認が終わったらterraform destroyを忘れずに!!
バケットを空にしないとバケットは削除できないのでまずindex.htmlを消してからdestroyしてください
補足
- パブリックアクセスブロックがアカウントレベルで有効
- この構成なら解除しなくても大丈夫です。誤って
public-read
ACL やパブリックポリシーを設定しようとしてもエラーになりますが、今回のようにすべて privateにしている分には問題ありません。
- この構成なら解除しなくても大丈夫です。誤って
- OAI (Origin Access Identity) と OAC (Origin Access Control)
- 2022年以降、OAC という新しい仕組みも登場しています。OAI が標準的ですが、将来的にOACへ移行するケースも増えるかもしれません。
- OACを使う場合は設定方法が少し異なりますが、**「バケットをprivateに保ち、CloudFrontだけがアクセスできる」**という基本コンセプトは同じです。
- S3の静的ウェブサイトホスティング自体は、パブリックアクセス前提で設計されている面があります。
- バケットに
website
ブロックやaws_s3_bucket_website_configuration
を設定しても、バケットそのものがprivateだと、ウェブサイトエンドポイントの直接アクセスは403になる場合が多いです。 - 実際にはCloudFront経由でサイトを公開するため、問題ありません。
- バケットに
- キャッシュ無効化など
- CloudFrontのキャッシュ設定やInvalidationについては別途検討してください。
- 変更が頻繁な場合は、キャッシュ期限を短くしたり、Terraform外で
aws cloudfront create-invalidation
を実行したりする運用が考えられます。
まとめ
- S3をprivateにし、Origin Access Identity (OAI) を使ってCloudFrontのみがS3にアクセスできるようにすることで、パブリックアクセスブロックを解除せずに安全に静的コンテンツを配信できます。
- Terraformモジュール化により、S3 と CloudFront の定義を分割しつつ、トップレベルの
main.tf
でシンプルに呼び出す構造にするのがおすすめ。 - 実行手順は
terraform init -> plan -> apply
の通常フローでOKですが、CloudFrontのデプロイには数分~10分程度かかることを理解しておきましょう。 - 出来上がったCloudFrontドメインにアクセスすれば、S3バケットを直接公開せずともHTTPSで静的サイトを配信できます。
というわけでえーあいさんに教えてもらいながら進めてみました。terraformのモジュールの書き方なども学んだのでもっとterraformについて知識を深めてみようと思います。