A layered Terraform setup for static website hosting on Google Cloud, composed of a GCS origin, External Global HTTPS Load Balancer, Cloud CDN, and Cloudflare DNS.
-
main– Production branch. This branch represents the stable and production-ready version of the code. It is used for deployments to the live environment. -
dev– Development branch. This is the default branch for ongoing development work. It is where new features and bug fixes are implemented and tested before being merged into the main branch. It is used for deployments to the staging environment.
Before applying this Terraform configuration, ensure the following tools and Google Cloud resources are set up. These steps prepare your local environment and GCP project for infrastructure provisioning.
- Terraform must be installed locally.
-
Install Terraform: https://developer.hashicorp.com/terraform/install?product_intent=terraform
-
Verify the installation:
terraform versionThe following steps document the one-time Google Cloud setup used for this project and are not required to run again.
-
Log in to the Google Cloud Console and create a new GCP project.
-
Ensure the project is linked to an active billing account.
-
Install Google Cloud CLI and authenticate to the project.
-
Enable Required APIs:
gcloud services enable compute.googleapis.com storage.googleapis.com dns.googleapis.com cloudresourcemanager.googleapis.com- Create a Service Account for Terraform to authenticate with Google Cloud and grant the following roles:
- Storage Admin (to manage Cloud Storage).
- Compute Admin (to manage Load Balancing and CDN).
- DNS Admin (to manage Cloud DNS).
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="serviceAccount:<SERVICE_ACCOUNT_EMAIL>" \
--role="roles/storage.admin"
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="serviceAccount:<SERVICE_ACCOUNT_EMAIL>" \
--role="roles/compute.admin"
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="serviceAccount:<SERVICE_ACCOUNT_EMAIL>" \
--role="roles/dns.admin"Terraform workflows are simplified via the root Makefile.
For example, to view the planned infrastructure changes for the staging environment:
make plan-simple-stagingReusable Terraform module responsible for:
- Creating a Google Cloud Storage bucket
- Uploading static website files from a specified build directory
- Setting correct Content-Type headers for all assets
- Configuring public read access
- Optionally enabling versioning and basic website configuration
This module acts as the origin layer for static assets and is intentionally front-door agnostic.
simple-infra composes the static_site_bucket module to provide basic static website hosting for a pre-built Next.js export.
It serves as a minimal static hosting setup and can later be placed behind different front-door architectures, such as:
- a Google Cloud HTTPS Load Balancer with Cloud CDN and Cloud DNS,
- a third-party edge platform (e.g. Cloudflare) handling DNS, TLS, and caching,
- or a custom application layer (e.g. Cloud Run or App Engine) serving or proxying the bucket contents.
edge-infra provisions the External Global HTTPS Load Balancer (Layer 7) that sits in front of the origin bucket.
It builds the serving layer using:
- Backend bucket - Registers the GCS bucket as the load balancer origin.
- URL map - Defines request routing rules.
- Target HTTPS proxy – Terminates TLS and routes traffic via the URL map.
- Target HTTP proxy (redirect) – Redirects all http:// traffic to https://.
- Global forwarding rule - Exposes a public IP (ports 80 / 443).
- Managed SSL certificate - Automatically provisions and renews HTTPS certificates.
- Cloud CDN – Caches static assets at Google’s global edge locations for improved performance and reduced origin load.
User Browser
↓
Cloudflare DNS (DNS-only mode)
↓
Google Global IP
↓
External HTTP(S) Load Balancer (HTTP redirects to HTTPS)
↓
Cloud CDN (edge cache)
↓
Backend Bucket
↓
GCS Static Assets
- simple-infra must already be applied (origin bucket exists)
- A domain must be purchased and accessible (e.g. via Cloudflare)
- Retrieve the Load Balancer IP:
- Create a DNS A record in Cloudflare: Domain → <LB_IP>
- Set to DNS-only (gray cloud)
- Wait for the managed SSL certificate to change from PROVISIONING to ACTIVE
- Verify redirect
curl -I http://<domain> → 301/308 to https://<domain>