Google Cloud Serverless Application
The Google Cloud Serverless Application template scaffolds a Pulumi project that deploys a Google Cloud Storage bucket configured for static website hosting and a Cloud Functions Gen 2 function for the application backend. The template ships with placeholder web and function content so the project deploys end to end out of the box.
Before you deploy#
Cloud Functions Gen 2 functions are gated at the underlying Cloud Run service, so the public-invoker grant in this template is created with gcp.cloudrun.IamMember (roles/run.invoker). The identity running pulumi up needs to be able to set IAM policy on Cloud Run — typically roles/run.admin, or a custom role that includes run.services.setIamPolicy — in addition to the permissions needed to create the function and storage resources.
Before you begin#
For this walkthrough, you'll need:
- The Pulumi CLI
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
For this walkthrough, you'll need:
- The Pulumi CLI
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
For this walkthrough, you'll need:
- The Pulumi CLI
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
- A current version of Go installed locally
For this walkthrough, you'll need:
- The Pulumi CLI
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
- A current version of .NET installed locally
For this walkthrough, you'll need:
- The Pulumi CLI
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
For this walkthrough, you'll need:
- A current version of the Pulumi CLI (Pulumi HCL requires no additional runtime)
- The gcloud CLI (optional, but recommended and used in this guide)
- Permission to manage Cloud Functions and Cloud Storage
Create a new project#
Create a new project from the template:
mkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-typescriptmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-typescriptmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-pythonmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-pythonmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-gomkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-gomkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-csharpmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-csharpmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-yamlmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-yamlmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-hclmkdir my-serverless-app; cd my-serverless-apppulumi new serverless-gcp-hclThe pulumi new command walks you through creating and configuring the new project and stack. A stack is an instance of a project, and you can have many of them (dev, staging, prod), each with its own configuration settings. Depending on the language, you may also be asked for your preferred package manager or toolchain.
This template’s settings include:
| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| gcp:region | The Google Cloud region to deploy into | us-central1 |
| sitePath | The path to the folder containing the website | www |
| appPath | The path to the folder containing the functions to be deployed | app |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set gcp:region <value>pulumi config set sitePath <value>pulumi config set appPath <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set gcp:project "<value>"pulumi config set gcp:region "<value>"pulumi config set sitePath "<value>"pulumi config set appPath "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| gcp:region | The Google Cloud region to deploy into | us-central1 |
| sitePath | The path to the folder containing the website | www |
| appPath | The path to the folder containing the functions to be deployed | app |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set gcp:region <value>pulumi config set sitePath <value>pulumi config set appPath <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set gcp:project "<value>"pulumi config set gcp:region "<value>"pulumi config set sitePath "<value>"pulumi config set appPath "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| gcp:region | The Google Cloud region to deploy into | us-central1 |
| sitePath | The path to the folder containing the website | www |
| appPath | The path to the folder containing the functions to be deployed | app |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set gcp:region <value>pulumi config set sitePath <value>pulumi config set appPath <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set gcp:project "<value>"pulumi config set gcp:region "<value>"pulumi config set sitePath "<value>"pulumi config set appPath "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| gcp:region | The Google Cloud region to deploy into | us-central1 |
| sitePath | The path to the folder containing the website | www |
| appPath | The path to the folder containing the functions to be deployed | app |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set gcp:region <value>pulumi config set sitePath <value>pulumi config set appPath <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set gcp:project "<value>"pulumi config set gcp:region "<value>"pulumi config set sitePath "<value>"pulumi config set appPath "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| gcp:region | The Google Cloud region to deploy into | us-central1 |
| sitePath | The path to the folder containing the website | www |
| appPath | The path to the folder containing the functions to be deployed | app |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set gcp:region <value>pulumi config set sitePath <value>pulumi config set appPath <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set gcp:project "<value>"pulumi config set gcp:region "<value>"pulumi config set sitePath "<value>"pulumi config set appPath "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| gcp:project | The Google Cloud project to deploy into | — |
| region | The Google Cloud region to deploy the function into | us-central1 |
| site_path | The path to the folder containing the website | www |
| app_path | The path to the folder containing the function to deploy | ./app |
| index_document | The file to use for top-level pages | index.html |
| error_document | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set gcp:project <value>pulumi config set region <value>pulumi config set site_path <value>pulumi config set app_path <value>pulumi config set index_document <value>pulumi config set error_document <value>pulumi config set gcp:project "<value>"pulumi config set region "<value>"pulumi config set site_path "<value>"pulumi config set app_path "<value>"pulumi config set index_document "<value>"pulumi config set error_document "<value>"For gcp:project, enter the ID of an existing Google Cloud project. gcloud config get-value project prints the one gcloud is using. Press Enter at the others to keep the defaults.
Review the code#
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the resources it declares.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
Take a moment to open them up and have a look. Here’s what’s in index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tf:
import * as pulumi from "@pulumi/pulumi";import * as gcp from "@pulumi/gcp";import * as synced from "@pulumi/synced-folder";
// Import the program's configuration settings.const config = new pulumi.Config();const sitePath = config.get("sitePath") || "www";const appPath = config.get("appPath") || "app";const indexDocument = config.get("indexDocument") || "index.html";const errorDocument = config.get("errorDocument") || "error.html";
// Create a storage bucket and configure it as a website.const siteBucket = new gcp.storage.Bucket("site-bucket", { location: "US", website: { mainPageSuffix: indexDocument, notFoundPage: errorDocument, },});
// Create an IAM binding to allow public read access to the bucket.const siteBucketIAMBinding = new gcp.storage.BucketIAMBinding("site-bucket-iam-binding", { bucket: siteBucket.name, role: "roles/storage.objectViewer", members: ["allUsers"],});
// Use a synced folder to manage the files of the website.const syncedFolder = new synced.GoogleCloudFolder("synced-folder", { path: sitePath, bucketName: siteBucket.name,});
// Create another storage bucket for the serverless app.const appBucket = new gcp.storage.Bucket("app-bucket", { location: "US",});
// Upload the serverless app to the storage bucket.const appArchive = new gcp.storage.BucketObject("app-archive", { bucket: appBucket.name, source: new pulumi.asset.FileArchive(appPath),});
// Create a Cloud Function (Gen 2) that returns some data.const dataFunction = new gcp.cloudfunctionsv2.Function("data-function", { location: gcp.config.region || "us-central1", buildConfig: { runtime: "nodejs24", entryPoint: "date", source: { storageSource: { bucket: appBucket.name, object: appArchive.name, }, }, }, serviceConfig: { availableMemory: "256M", timeoutSeconds: 60, },});
// Allow public, unauthenticated invocations of the underlying Cloud Run service.const invoker = new gcp.cloudrun.IamMember("data-function-invoker", { location: dataFunction.location, service: dataFunction.name, role: "roles/run.invoker", member: "allUsers",});
// Create a JSON configuration file for the website.const siteConfig = new gcp.storage.BucketObject("site-config", { name: "config.json", source: dataFunction.url.apply(url => new pulumi.asset.StringAsset(JSON.stringify({ api: url })), ), contentType: "application/json", bucket: siteBucket.name,});
// Export the URLs of the website and serverless endpoint.export const siteURL = pulumi.interpolate`https://storage.googleapis.com/${siteBucket.name}/index.html`;export const apiURL = dataFunction.url;import pulumiimport pulumi_gcp as gcpimport pulumi_synced_folder as synced
# Import the program's configuration settings.config = pulumi.Config()site_path = config.get("sitePath", "www")app_path = config.get("appPath", "app")index_document = config.get("indexDocument", "index.html")error_document = config.get("errorDocument", "error.html")region = gcp.config.region or "us-central1"
# Create a storage bucket and configure it as a website.site_bucket = gcp.storage.Bucket( "site-bucket", location="US", website={ "main_page_suffix": index_document, "not_found_page": error_document, },)
# Create an IAM binding to allow public read access to the bucket.site_bucket_iam_binding = gcp.storage.BucketIAMBinding( "site-bucket-iam-binding", bucket=site_bucket.name, role="roles/storage.objectViewer", members=["allUsers"],)
# Use a synced folder to manage the files of the website.synced_folder = synced.GoogleCloudFolder( "synced-folder", path=site_path, bucket_name=site_bucket.name,)
# Create another storage bucket for the serverless app.app_bucket = gcp.storage.Bucket( "app-bucket", location="US",)
# Upload the serverless app to the storage bucket.app_archive = gcp.storage.BucketObject( "app-archive", bucket=app_bucket.name, source=pulumi.asset.FileArchive(app_path),)
# Create a Cloud Function (Gen 2) that returns some data.data_function = gcp.cloudfunctionsv2.Function( "data-function", location=region, build_config={ "runtime": "python312", "entry_point": "data", "source": { "storage_source": { "bucket": app_bucket.name, "object": app_archive.name, }, }, }, service_config={ "available_memory": "256M", "timeout_seconds": 60, },)
# Allow public, unauthenticated invocations of the underlying Cloud Run service.invoker = gcp.cloudrun.IamMember( "data-function-invoker", location=data_function.location, service=data_function.name, role="roles/run.invoker", member="allUsers",)
# Create a JSON configuration file for the website.site_config = gcp.storage.BucketObject( "site-config", name="config.json", bucket=site_bucket.name, content_type="application/json", source=data_function.url.apply( lambda url: pulumi.StringAsset('{ "api": "' + url + '" }') ),)
# Export the URLs of the website and serverless endpoint.pulumi.export( "siteURL", site_bucket.name.apply( lambda name: f"https://storage.googleapis.com/{name}/index.html" ),)pulumi.export("apiURL", data_function.url)package main
import ( "fmt"
"github.com/pulumi/pulumi-gcp/sdk/v10/go/gcp/cloudfunctionsv2" "github.com/pulumi/pulumi-gcp/sdk/v10/go/gcp/cloudrun" "github.com/pulumi/pulumi-gcp/sdk/v10/go/gcp/storage" synced "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config")
func main() { pulumi.Run(func(ctx *pulumi.Context) error {
// Import the program's configuration settings. cfg := config.New(ctx, "") sitePath := "www" if param := cfg.Get("sitePath"); param != "" { sitePath = param } appPath := "app" if param := cfg.Get("appPath"); param != "" { appPath = param } indexDocument := "index.html" if param := cfg.Get("indexDocument"); param != "" { indexDocument = param } errorDocument := "error.html" if param := cfg.Get("errorDocument"); param != "" { errorDocument = param }
gcpCfg := config.New(ctx, "gcp") region := gcpCfg.Get("region") if region == "" { region = "us-central1" }
// Create a storage bucket and configure it as a website. siteBucket, err := storage.NewBucket(ctx, "site-bucket", &storage.BucketArgs{ Location: pulumi.String("US"), Website: &storage.BucketWebsiteArgs{ MainPageSuffix: pulumi.String(indexDocument), NotFoundPage: pulumi.String(errorDocument), }, }) if err != nil { return err }
// Create an IAM binding to allow public read access to the bucket. _, err = storage.NewBucketIAMBinding(ctx, "site-bucket-iam-binding", &storage.BucketIAMBindingArgs{ Bucket: siteBucket.Name, Role: pulumi.String("roles/storage.objectViewer"), Members: pulumi.StringArray{ pulumi.String("allUsers"), }, }) if err != nil { return err }
// Use a synced folder to manage the files of the website. _, err = synced.NewGoogleCloudFolder(ctx, "synced-folder", &synced.GoogleCloudFolderArgs{ Path: pulumi.String(sitePath), BucketName: siteBucket.Name, }) if err != nil { return err }
// Create another storage bucket for the serverless app. appBucket, err := storage.NewBucket(ctx, "app-bucket", &storage.BucketArgs{ Location: pulumi.String("US"), }) if err != nil { return err }
// Upload the serverless app to the storage bucket. appArchive, err := storage.NewBucketObject(ctx, "app-archive", &storage.BucketObjectArgs{ Bucket: appBucket.Name, Source: pulumi.NewFileArchive(appPath), }) if err != nil { return err }
// Create a Cloud Function (Gen 2) that returns some data. dataFunction, err := cloudfunctionsv2.NewFunction(ctx, "data-function", &cloudfunctionsv2.FunctionArgs{ Location: pulumi.String(region), BuildConfig: &cloudfunctionsv2.FunctionBuildConfigArgs{ Runtime: pulumi.String("go126"), EntryPoint: pulumi.String("Data"), Source: &cloudfunctionsv2.FunctionBuildConfigSourceArgs{ StorageSource: &cloudfunctionsv2.FunctionBuildConfigSourceStorageSourceArgs{ Bucket: appBucket.Name, Object: appArchive.Name, }, }, }, ServiceConfig: &cloudfunctionsv2.FunctionServiceConfigArgs{ AvailableMemory: pulumi.String("256M"), TimeoutSeconds: pulumi.Int(60), }, }) if err != nil { return err }
// Allow public, unauthenticated invocations of the underlying Cloud Run service. _, err = cloudrun.NewIamMember(ctx, "data-function-invoker", &cloudrun.IamMemberArgs{ Location: dataFunction.Location, Service: dataFunction.Name, Role: pulumi.String("roles/run.invoker"), Member: pulumi.String("allUsers"), }) if err != nil { return err }
// Create a JSON configuration file for the website. _, err = storage.NewBucketObject(ctx, "site-config", &storage.BucketObjectArgs{ Name: pulumi.String("config.json"), Bucket: siteBucket.Name, ContentType: pulumi.String("application/json"), Source: dataFunction.Url.ApplyT(func(url string) pulumi.AssetOrArchiveOutput { config := fmt.Sprintf(`{ "api": "%s" }`, url) return pulumi.NewStringAsset(config).ToAssetOrArchiveOutput() }).(pulumi.AssetOrArchiveOutput), }) if err != nil { return err }
// Export the URLs of the website and serverless endpoint. ctx.Export("siteURL", pulumi.Sprintf("https://storage.googleapis.com/%s/index.html", siteBucket.Name)) ctx.Export("apiURL", dataFunction.Url)
return nil })}using System.Collections.Generic;using System.Text.Json;using Pulumi;using Gcp = Pulumi.Gcp;using SyncedFolder = Pulumi.SyncedFolder;
return await Deployment.RunAsync(() =>{ // Import the program's configuration settings. var config = new Config(); var sitePath = config.Get("sitePath") ?? "www"; var appPath = config.Get("appPath") ?? "app"; var indexDocument = config.Get("indexDocument") ?? "index.html"; var errorDocument = config.Get("errorDocument") ?? "error.html"; var region = new Config("gcp").Get("region") ?? "us-central1";
// Create a storage bucket and configure it as a website. var siteBucket = new Gcp.Storage.Bucket("site-bucket", new() { Location = "US", Website = new Gcp.Storage.Inputs.BucketWebsiteArgs { MainPageSuffix = indexDocument, NotFoundPage = errorDocument, }, });
// Create an IAM binding to allow public read access to the bucket. var siteBucketIAMBinding = new Gcp.Storage.BucketIAMBinding("site-bucket-iam-binding", new() { Bucket = siteBucket.Name, Role = "roles/storage.objectViewer", Members = new[] { "allUsers", }, });
// Use a synced folder to manage the files of the website. var syncedFolder = new SyncedFolder.GoogleCloudFolder("synced-folder", new() { Path = sitePath, BucketName = siteBucket.Name, });
// Create another storage bucket for the serverless app. var appBucket = new Gcp.Storage.Bucket("app-bucket", new() { Location = "US", });
// Upload the serverless app to the storage bucket. var appArchive = new Gcp.Storage.BucketObject("app-archive", new() { Bucket = appBucket.Name, Source = new Pulumi.FileArchive(appPath), });
// Create a Cloud Function (Gen 2) that returns some data. var dataFunction = new Gcp.CloudFunctionsV2.Function("data-function", new() { Location = region, BuildConfig = new Gcp.CloudFunctionsV2.Inputs.FunctionBuildConfigArgs { Runtime = "dotnet10", EntryPoint = "App.Data", Source = new Gcp.CloudFunctionsV2.Inputs.FunctionBuildConfigSourceArgs { StorageSource = new Gcp.CloudFunctionsV2.Inputs.FunctionBuildConfigSourceStorageSourceArgs { Bucket = appBucket.Name, Object = appArchive.Name, }, }, }, ServiceConfig = new Gcp.CloudFunctionsV2.Inputs.FunctionServiceConfigArgs { AvailableMemory = "256M", TimeoutSeconds = 60, }, });
// Allow public, unauthenticated invocations of the underlying Cloud Run service. var invoker = new Gcp.CloudRun.IamMember("data-function-invoker", new() { Location = dataFunction.Location, Service = dataFunction.Name, Role = "roles/run.invoker", Member = "allUsers", });
// Create a JSON configuration file for the website. var siteConfig = new Gcp.Storage.BucketObject("site-config", new() { Name = "config.json", ContentType = "application/json", Bucket = siteBucket.Name, Source = dataFunction.Url.Apply(url => { var config = JsonSerializer.Serialize(new { api = url }); return new Pulumi.StringAsset(config) as AssetOrArchive; }), });
// Export the URLs of the website and serverless endpoint. return new Dictionary<string, object?> { ["siteURL"] = siteBucket.Name.Apply(name => $"https://storage.googleapis.com/{name}/index.html"), ["apiURL"] = dataFunction.Url, };});name: my-projectdescription: A Pulumi YAML program to deploy a serverless application on Google Cloudruntime: yaml
# Import the program's configuration settings.config: sitePath: type: string default: www appPath: type: string default: app indexDocument: type: string default: index.html errorDocument: type: string default: error.html
variables:
# Create a FileArchive containing the contents of the app folder. appArchive: fn::fileArchive: ${appPath}
# Create a JSON string containing the URL of the serverless app. configJSON: fn::toJSON: api: ${data-function.url}
# Create a StringAsset resource to convert the JSON string into a file. configFile: fn::stringAsset: ${configJSON}
resources:
# Create a storage bucket and configure it as a website. site-bucket: type: gcp:storage:Bucket properties: location: US website: mainPageSuffix: ${indexDocument} notFoundPage: ${errorDocument}
# Create an IAM binding to allow public read access to the bucket. site-bucket-iam-binding: type: gcp:storage:BucketIAMBinding properties: bucket: ${site-bucket.name} role: roles/storage.objectViewer members: - allUsers
# Use a synced folder to manage the files of the website. synced-folder: type: synced-folder:index:GoogleCloudFolder properties: path: ${sitePath} bucketName: ${site-bucket.name}
# Create another storage bucket for the serverless app. app-bucket: type: gcp:storage:Bucket properties: location: US
# Upload the serverless app to the storage bucket. app-archive: type: gcp:storage:BucketObject properties: bucket: ${app-bucket.name} source: ${appArchive}
# Create a Cloud Function (Gen 2) that returns some data. data-function: type: gcp:cloudfunctionsv2:Function properties: location: ${gcp:region} buildConfig: runtime: python312 entryPoint: data source: storageSource: bucket: ${app-bucket.name} object: ${app-archive.name} serviceConfig: availableMemory: 256M timeoutSeconds: 60
# Allow public, unauthenticated invocations of the underlying Cloud Run service. invoker: type: gcp:cloudrun:IamMember properties: location: ${data-function.location} service: ${data-function.name} role: roles/run.invoker member: allUsers
# Create a JSON configuration file for the website. site-config: type: gcp:storage:BucketObject properties: name: config.json bucket: ${site-bucket.name} contentType: application/json source: ${configFile}
# Export the URLs of the website and serverless endpoint.outputs: siteURL: https://storage.googleapis.com/${site-bucket.name}/index.html apiURL: ${data-function.url}terraform { required_providers { gcp = { source = "pulumi/gcp" } synced-folder = { source = "pulumi/synced-folder" } }}
variable "region" { description = "The Google Cloud region to deploy into" type = string default = "us-central1"}
variable "site_path" { description = "The path to the folder containing the website" type = string default = "www"}
variable "app_path" { description = "The path to the folder containing the functions to be deployed" type = string default = "./app"}
variable "index_document" { description = "The file to use for top-level pages" type = string default = "index.html"}
variable "error_document" { description = "The file to use for error pages" type = string default = "error.html"}
# Create a storage bucket and configure it as a website.resource "gcp_storage_bucket" "site-bucket" { location = "US"
website = { main_page_suffix = var.index_document not_found_page = var.error_document }}
# Create an IAM binding to allow public read access to the bucket.resource "gcp_storage_bucket_i_a_m_binding" "site-bucket-iam-binding" { bucket = gcp_storage_bucket.site-bucket.name role = "roles/storage.objectViewer" members = ["allUsers"]}
# Use a synced folder to manage the files of the website.resource "synced-folder_google_cloud_folder" "synced-folder" { path = var.site_path bucket_name = gcp_storage_bucket.site-bucket.name}
# Create another storage bucket for the serverless app.resource "gcp_storage_bucket" "app-bucket" { location = "US"}
# Upload the serverless app to the storage bucket.resource "gcp_storage_bucket_object" "app-archive" { bucket = gcp_storage_bucket.app-bucket.name source = filearchive(var.app_path)}
# Create a Cloud Function (Gen 2) that returns some data.resource "gcp_cloudfunctionsv2_function" "data-function" { location = var.region
build_config = { runtime = "python312" entry_point = "data" source = { storage_source = { bucket = gcp_storage_bucket.app-bucket.name object = gcp_storage_bucket_object.app-archive.name } } }
service_config = { available_memory = "256M" timeout_seconds = 60 }}
# Allow public, unauthenticated invocations of the underlying Cloud Run service.resource "gcp_cloudrun_iam_member" "invoker" { location = gcp_cloudfunctionsv2_function.data-function.location service = gcp_cloudfunctionsv2_function.data-function.name role = "roles/run.invoker" member = "allUsers"}
# Create a JSON configuration file for the website.resource "gcp_storage_bucket_object" "site-config" { name = "config.json" bucket = gcp_storage_bucket.site-bucket.name content_type = "application/json" source = stringasset(jsonencode({ api = gcp_cloudfunctionsv2_function.data-function.url }))}
# Export the URLs of the website and serverless endpoint.output "site_url" { value = "https://storage.googleapis.com/${gcp_storage_bucket.site-bucket.name}/${var.index_document}"}
output "api_url" { value = gcp_cloudfunctionsv2_function.data-function.url}Deploy the project#
First, be sure to enable the Cloud Functions, Cloud Build, Artifact Registry and Cloud Run APIs in your project:
gcloud services enable cloudfunctions.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com run.googleapis.com --project $(pulumi config get gcp:project)gcloud services enable cloudfunctions.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com run.googleapis.com --project $(pulumi config get gcp:project)Deploy with pulumi up. It starts with a preview of what it’s about to create and asks before it changes anything. Choose yes to deploy.
pulumi uppulumi upPulumi adds a random suffix to most resource names, so the Cloud Function data-function becomes something like data-function-4f8a2c1. This is autonaming. It lets you deploy the same program to more than one stack, and lets Pulumi create a replacement before it deletes the old resource, without names colliding.
When it finishes, Pulumi prints the stack’s outputs, values it only knows once the resources exist:
- siteURL
The HTTPS URL of the static website.
- apiURL
The HTTPS URL of the serverless function endpoint.
- siteURL
The HTTPS URL of the static website.
- apiURL
The HTTPS URL of the serverless function endpoint.
- siteURL
The HTTPS URL of the static website.
- apiURL
The HTTPS URL of the serverless function endpoint.
- siteURL
The HTTPS URL of the static website.
- apiURL
The HTTPS URL of the serverless function endpoint.
- siteURL
The HTTPS URL of the static website.
- apiURL
The HTTPS URL of the serverless function endpoint.
- site_url
The HTTPS URL of the static website.
- api_url
The HTTPS URL of the serverless function endpoint.
Read one any time with pulumi stack output <name>, or pass it to another command. This opens the website in your browser:
open $(pulumi stack output siteURL)xdg-open $(pulumi stack output siteURL)Start-Process (pulumi stack output siteURL)open $(pulumi stack output siteURL)xdg-open $(pulumi stack output siteURL)Start-Process (pulumi stack output siteURL)open $(pulumi stack output siteURL)xdg-open $(pulumi stack output siteURL)Start-Process (pulumi stack output siteURL)open $(pulumi stack output siteURL)xdg-open $(pulumi stack output siteURL)Start-Process (pulumi stack output siteURL)open $(pulumi stack output siteURL)xdg-open $(pulumi stack output siteURL)Start-Process (pulumi stack output siteURL)open $(pulumi stack output site_url)xdg-open $(pulumi stack output site_url)Start-Process (pulumi stack output site_url)The website gets the time from the function. Call the function from the terminal with curl; it prints the current time in milliseconds, like {"now": 1792591509000}:
curl -s $(pulumi stack output apiURL)curl.exe -s (pulumi stack output apiURL)curl -s $(pulumi stack output apiURL)curl.exe -s (pulumi stack output apiURL)curl -s $(pulumi stack output apiURL)curl.exe -s (pulumi stack output apiURL)curl -s $(pulumi stack output apiURL)curl.exe -s (pulumi stack output apiURL)curl -s $(pulumi stack output apiURL)curl.exe -s (pulumi stack output apiURL)curl -s $(pulumi stack output api_url)curl.exe -s (pulumi stack output api_url)Need help?
If pulumi up reports failed to load application credentials, sign in again:
gcloud auth application-default logingcloud auth application-default loginStill stuck? Let us know.
Customize the project#
Projects created with this template expose the following configuration settings:
- gcp:region
The Google Cloud region to deploy into. Defaults to
us-central1.- sitePath
The path to the folder containing the files of the website. Defaults to
www, which is the name (and relative path) of the folder included with the template.- appPath
The path to the folder containing the serverless functions to be deployed. Defaults to
app, which is also included with the template.- indexDocument
The file to use for top-level pages. Defaults to
index.html.- errorDocument
The file to use for error pages. Defaults to
error.html.
- gcp:region
The Google Cloud region to deploy into. Defaults to
us-central1.- sitePath
The path to the folder containing the files of the website. Defaults to
www, which is the name (and relative path) of the folder included with the template.- appPath
The path to the folder containing the serverless functions to be deployed. Defaults to
app, which is also included with the template.- indexDocument
The file to use for top-level pages. Defaults to
index.html.- errorDocument
The file to use for error pages. Defaults to
error.html.
- gcp:region
The Google Cloud region to deploy into. Defaults to
us-central1.- sitePath
The path to the folder containing the files of the website. Defaults to
www, which is the name (and relative path) of the folder included with the template.- appPath
The path to the folder containing the serverless functions to be deployed. Defaults to
app, which is also included with the template.- indexDocument
The file to use for top-level pages. Defaults to
index.html.- errorDocument
The file to use for error pages. Defaults to
error.html.
- gcp:region
The Google Cloud region to deploy into. Defaults to
us-central1.- sitePath
The path to the folder containing the files of the website. Defaults to
www, which is the name (and relative path) of the folder included with the template.- appPath
The path to the folder containing the serverless functions to be deployed. Defaults to
app, which is also included with the template.- indexDocument
The file to use for top-level pages. Defaults to
index.html.- errorDocument
The file to use for error pages. Defaults to
error.html.
- gcp:region
The Google Cloud region to deploy into. Defaults to
us-central1.- sitePath
The path to the folder containing the files of the website. Defaults to
www, which is the name (and relative path) of the folder included with the template.- appPath
The path to the folder containing the serverless functions to be deployed. Defaults to
app, which is also included with the template.- indexDocument
The file to use for top-level pages. Defaults to
index.html.- errorDocument
The file to use for error pages. Defaults to
error.html.
- region
The Google Cloud region to deploy into. Defaults to
us-central1.- site_path
The path to the folder containing the files of the website. Defaults to
www, which is the relative path of the folder included with the template.- app_path
The path to the folder containing the serverless functions to be deployed. Defaults to
./app, which is also included with the template.- index_document
The file to use for top-level pages. Defaults to
index.html.- error_document
The file to use for error pages. Defaults to
error.html.
All of these settings are optional and may be adjusted either by editing the stack configuration file directly (by default, Pulumi.dev.yaml) or by changing their values with pulumi config set.
For example, to deploy a website from another folder on your computer, replacing ../my-existing-website/build with the path to your site’s files:
pulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set sitePath ../my-existing-website/buildpulumi uppulumi config set site_path ../my-existing-website/buildpulumi uppulumi config set site_path ../my-existing-website/buildpulumi upMoving the function to another region#
The region setting decides where the function runs. Change it to europe-west1 and deploy: Pulumi creates the function in the new region, then deletes the old one. gcloud functions list then shows it in europe-west1:
pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set gcp:region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)pulumi config set region europe-west1pulumi upgcloud functions list --project $(pulumi config get gcp:project)To see what else you can add, browse the Google Cloud provider in the Pulumi Registry.
Cleaning up#
You can cleanly destroy the stack and all of its infrastructure with pulumi destroy:
pulumi destroypulumi destroyLearn more#
- Browse other architecture templates in the Templates gallery.
- Explore the Google Cloud provider API docs in the Pulumi Registry.
- Walk through Pulumi from the ground up in Pulumi Tutorials.
- Read the latest Google Cloud posts on the Pulumi blog.