☁️Static Pages Hosting by Cloud Run

The static site hosting example by Cloud Run and the custom domain setting

Overview 

Prerequisites 

How to Build the Static Page Site 

1. Enable API 

To use the Cloud DNS service and manage the resource by the Tofu or Terraform or something, a developer need to enable the Cloud DNS service.

 1// Variable for GCP
 2variable "gcp_project" {
 3  type = object({
 4    project     = string   // The gcp project name
 5    region      = string   // The region of gcp project
 6  })
 7}
 8
 9variable "gcp_services" {
10  type = list(string)      // Enabling the service name of api
11}
12
13// Enable APIs
14resource "google_project_service" "api_enable" {
15  for_each = toset(var.gcp_services)
16  project  = var.gcp_project.project
17  service  = each.value
18}

2. Prepare the static pages container 

In this part, to build simple file http server, this part share golang example code. You can choose any language or a web server container for this.

 1package main
 2
 3import (
 4	"embed"
 5	"flag"
 6	"fmt"
 7	"io/fs"
 8	"log"
 9	"net/http"
10	"os"
11)
12
13//go:embed pub
14var staticFs embed.FS
15
16func fileHandler() http.HandlerFunc {
17	pub, _ := fs.Sub(staticFs, "pub")
18	handler := http.FileServerFS(pub)
19
20	return func(w http.ResponseWriter, req *http.Request) {
21		w.Header().Set("Cache-Control", "public, max-age=3600")
22		handler.ServeHTTP(w, req)
23	}
24}
25
26func main() {
27	portNum := flag.String("p", "8080", "server port")
28	host := flag.String("i", "", "interface name")
29	flag.Parse()
30
31	portEnv := os.Getenv("PORT")
32	if len(portEnv) > 0 {
33		*portNum = portEnv
34	}
35
36	srv := fmt.Sprintf("%s:%s", *host, *portNum)
37	log.Printf("server: %s", srv)
38
39	handler := fileHandler()
40	http.Handle("/", handler)
41
42	if err := http.ListenAndServe(srv, nil); err != nil {
43		log.Fatal(err)
44	}
45}

The below command is the bhild check on local

1go build -o chk ./cmd/main.go

3. Set up the Artifact Registry 

To run the file server on Cloud Run, the Artifact Registry is required. to store the file server container, this part builds the Artifact Registry.

 1variable "run" {
 2  type = object({
 3    name     = string
 4    project  = string
 5    location = string
 6    image    = string
 7    args     = list(string)
 8    port     = optional(number, 8080)
 9    allusers = optional(bool, true)
10    ingress  = optional(string, "INGRESS_TRAFFIC_ALL")
11    instance = optional(
12      object({ min = number, max = number, cpu = string, mem = string }),
13      { min = 0, max = 2, cpu = "1", mem = "512Mi" }
14    )
15
16    domain = optional(object({
17      name      = string
18      namespace = string
19    }))
20
21    // AR
22    tags            = optional(list(string), ["v", "latest"])
23    retation_period = optional(string, "86400s")
24  })
25}
 1locals {
 2  repo_name = "${var.run.name}-repo"
 3  ar_url    = "${var.run.location}-docker.pkg.dev/${var.run.project}/${local.repo_name}/${var.run.image}"
 4}
 5
 6resource "google_artifact_registry_repository" "repo" {
 7  location      = var.run.location
 8  repository_id = "${var.run.name}-repo"
 9  description   = "The repository for the Cloud Run Services"
10  format        = "DOCKER"
11
12
13  cleanup_policies {
14    id     = "keep-tagged-version"
15    action = "KEEP"
16    condition {
17      tag_state    = "TAGGED"
18      tag_prefixes = var.run.tags
19    }
20  }
21  cleanup_policies {
22    id     = "delete-any"
23    action = "DELETE"
24    condition {
25      tag_state  = "ANY"
26      older_than = var.run.retation_period // 1 day
27    }
28  }
29}

4. Build the Cloud Run 

To run the file server container, this part builds the Cloud Run Service.

 1resource "google_cloud_run_v2_service" "app" {
 2  depends_on = [google_artifact_registry_repository.repo]
 3
 4  name     = var.run.name
 5  location = var.run.location
 6  ingress  = var.run.ingress
 7  template {
 8    containers {
 9      name = var.run.name
10      ports {
11        container_port = var.run.port
12      }
13      image = "${local.ar_url}:latest"
14
15      resources {
16        limits = {
17          memory = var.run.instance.mem
18          cpu    = var.run.instance.cpu
19        }
20      }
21    }
22
23    scaling {
24      min_instance_count = var.run.instance.min
25      max_instance_count = var.run.instance.max
26    }
27  }
28
29  lifecycle {
30    ignore_changes = [
31      template[0].containers[0].image
32    ]
33  }
34}
35
36resource "google_cloud_run_v2_service_iam_member" "app" {
37  depends_on = [google_cloud_run_v2_service.app]
38  count      = var.run.allusers ? 1 : 0
39  location   = google_cloud_run_v2_service.app.location
40  name       = google_cloud_run_v2_service.app.name
41  role       = "roles/run.invoker"
42  member     = "allUsers"
43}

5. Set up a Custom domain 

To allow domain name access, this part set up custom domain configuration.

 1variable "domain" {
 2	type = object({
 3		zone_name = string
 4		dns_name = string
 5		description = string
 6		search_console_code = string
 7	})
 8}
 9
10resource "google_dns_managed_zone" "content" {
11  project     = var.gcp_project.project
12  name        = var.domain.zone_name
13  dns_name    = var.domain.dns_name
14  description = var.domain.descriotpion
15  labels = {
16    domain = "media"
17  }
18}
19
20resource "google_dns_record_set" "gcs_hosting_txt" {
21  name         = var.domain.dns_name
22  type         = "TXT"
23  ttl          = 600
24  managed_zone = google_dns_managed_zone.content.name
25  rrdatas      = [var.domain.search_console_code]
26}
27
28resource "google_dns_record_set" "run_pages" {
29  name         = var.domain.dns_name
30  type         = "CNAME"
31  ttl          = 300
32  managed_zone = google_dns_managed_zone.content.name
33  rrdatas      = ["ghs.googlehosted.com."]
34}
 1resource "google_cloud_run_domain_mapping" "custom" {
 2  depends_on = [google_cloud_run_v2_service.app]
 3  for_each   = var.run.domain == null ? {} : { "dns" : var.run.domain }
 4  name       = each.value.name
 5  location   = var.run.location
 6  metadata {
 7    namespace = each.value.namespace
 8  }
 9  spec {
10    route_name = google_cloud_run_v2_service.app.name
11  }
12}

SEE ALSO 

GCP   StaticPages  

Memo / Google Cloud Platform / Static Pages Hosting by Cloud Run