The gcp:firebase/appHostingBuild:AppHostingBuild resource, part of the Pulumi GCP provider, represents a single build for a Firebase App Hosting backend. Each build encapsulates an Artifact Registry container image, a Cloud Build invocation, and a Cloud Run revision at a specific point in time. This guide focuses on three capabilities: deploying pre-built container images, adding metadata with annotations and labels, and building from GitHub source code.
Builds depend on an existing AppHostingBackend resource, a service account with the firebaseapphosting.computeRunner role, and either a container image in Artifact Registry or a GitHub repository connection via Developer Connect. The examples are intentionally small. Combine them with your own backend configuration and deployment workflows.
Deploy a pre-built container image
Teams often validate their backend configuration by deploying existing container images before setting up automated builds from source.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
//## Include these blocks only once per project if you are starting from scratch ###
const serviceAccount = new gcp.serviceaccount.Account("service_account", {
project: "my-project-name",
accountId: "firebase-app-hosting-compute",
displayName: "Firebase App Hosting compute service account",
createIgnoreAlreadyExists: true,
});
const fah = new gcp.projects.Service("fah", {
project: "my-project-name",
service: "firebaseapphosting.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
project: "my-project-name",
location: "us-central1",
backendId: "mini",
appId: "1:0000000000:web:674cde32020e16fbce9dbd",
servingLocality: "GLOBAL_ACCESS",
serviceAccount: serviceAccount.email,
}, {
dependsOn: [fah],
});
const example = new gcp.firebase.AppHostingBuild("example", {
project: exampleAppHostingBackend.project,
location: exampleAppHostingBackend.location,
backend: exampleAppHostingBackend.backendId,
buildId: "mini-build",
source: {
container: {
image: "us-docker.pkg.dev/cloudrun/container/hello",
},
},
});
const appHostingSaRunner = new gcp.projects.IAMMember("app_hosting_sa_runner", {
project: "my-project-name",
role: "roles/firebaseapphosting.computeRunner",
member: serviceAccount.member,
});
import pulumi
import pulumi_gcp as gcp
### Include these blocks only once per project if you are starting from scratch ###
service_account = gcp.serviceaccount.Account("service_account",
project="my-project-name",
account_id="firebase-app-hosting-compute",
display_name="Firebase App Hosting compute service account",
create_ignore_already_exists=True)
fah = gcp.projects.Service("fah",
project="my-project-name",
service="firebaseapphosting.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
project="my-project-name",
location="us-central1",
backend_id="mini",
app_id="1:0000000000:web:674cde32020e16fbce9dbd",
serving_locality="GLOBAL_ACCESS",
service_account=service_account.email,
opts = pulumi.ResourceOptions(depends_on=[fah]))
example = gcp.firebase.AppHostingBuild("example",
project=example_app_hosting_backend.project,
location=example_app_hosting_backend.location,
backend=example_app_hosting_backend.backend_id,
build_id="mini-build",
source={
"container": {
"image": "us-docker.pkg.dev/cloudrun/container/hello",
},
})
app_hosting_sa_runner = gcp.projects.IAMMember("app_hosting_sa_runner",
project="my-project-name",
role="roles/firebaseapphosting.computeRunner",
member=service_account.member)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// ## Include these blocks only once per project if you are starting from scratch ###
serviceAccount, err := serviceaccount.NewAccount(ctx, "service_account", &serviceaccount.AccountArgs{
Project: pulumi.String("my-project-name"),
AccountId: pulumi.String("firebase-app-hosting-compute"),
DisplayName: pulumi.String("Firebase App Hosting compute service account"),
CreateIgnoreAlreadyExists: pulumi.Bool(true),
})
if err != nil {
return err
}
fah, err := projects.NewService(ctx, "fah", &projects.ServiceArgs{
Project: pulumi.String("my-project-name"),
Service: pulumi.String("firebaseapphosting.googleapis.com"),
})
if err != nil {
return err
}
exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
BackendId: pulumi.String("mini"),
AppId: pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
ServingLocality: pulumi.String("GLOBAL_ACCESS"),
ServiceAccount: serviceAccount.Email,
}, pulumi.DependsOn([]pulumi.Resource{
fah,
}))
if err != nil {
return err
}
_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
Project: exampleAppHostingBackend.Project,
Location: exampleAppHostingBackend.Location,
Backend: exampleAppHostingBackend.BackendId,
BuildId: pulumi.String("mini-build"),
Source: &firebase.AppHostingBuildSourceArgs{
Container: &firebase.AppHostingBuildSourceContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/hello"),
},
},
})
if err != nil {
return err
}
_, err = projects.NewIAMMember(ctx, "app_hosting_sa_runner", &projects.IAMMemberArgs{
Project: pulumi.String("my-project-name"),
Role: pulumi.String("roles/firebaseapphosting.computeRunner"),
Member: serviceAccount.Member,
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
//## Include these blocks only once per project if you are starting from scratch ###
var serviceAccount = new Gcp.ServiceAccount.Account("service_account", new()
{
Project = "my-project-name",
AccountId = "firebase-app-hosting-compute",
DisplayName = "Firebase App Hosting compute service account",
CreateIgnoreAlreadyExists = true,
});
var fah = new Gcp.Projects.Service("fah", new()
{
Project = "my-project-name",
ServiceName = "firebaseapphosting.googleapis.com",
});
var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
{
Project = "my-project-name",
Location = "us-central1",
BackendId = "mini",
AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
ServingLocality = "GLOBAL_ACCESS",
ServiceAccount = serviceAccount.Email,
}, new CustomResourceOptions
{
DependsOn =
{
fah,
},
});
var example = new Gcp.Firebase.AppHostingBuild("example", new()
{
Project = exampleAppHostingBackend.Project,
Location = exampleAppHostingBackend.Location,
Backend = exampleAppHostingBackend.BackendId,
BuildId = "mini-build",
Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
{
Container = new Gcp.Firebase.Inputs.AppHostingBuildSourceContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/hello",
},
},
});
var appHostingSaRunner = new Gcp.Projects.IAMMember("app_hosting_sa_runner", new()
{
Project = "my-project-name",
Role = "roles/firebaseapphosting.computeRunner",
Member = serviceAccount.Member,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.projects.Service;
import com.pulumi.gcp.projects.ServiceArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceContainerArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
//## Include these blocks only once per project if you are starting from scratch ###
var serviceAccount = new Account("serviceAccount", AccountArgs.builder()
.project("my-project-name")
.accountId("firebase-app-hosting-compute")
.displayName("Firebase App Hosting compute service account")
.createIgnoreAlreadyExists(true)
.build());
var fah = new Service("fah", ServiceArgs.builder()
.project("my-project-name")
.service("firebaseapphosting.googleapis.com")
.build());
var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
.project("my-project-name")
.location("us-central1")
.backendId("mini")
.appId("1:0000000000:web:674cde32020e16fbce9dbd")
.servingLocality("GLOBAL_ACCESS")
.serviceAccount(serviceAccount.email())
.build(), CustomResourceOptions.builder()
.dependsOn(fah)
.build());
var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
.project(exampleAppHostingBackend.project())
.location(exampleAppHostingBackend.location())
.backend(exampleAppHostingBackend.backendId())
.buildId("mini-build")
.source(AppHostingBuildSourceArgs.builder()
.container(AppHostingBuildSourceContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/hello")
.build())
.build())
.build());
var appHostingSaRunner = new IAMMember("appHostingSaRunner", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/firebaseapphosting.computeRunner")
.member(serviceAccount.member())
.build());
}
}
resources:
example:
type: gcp:firebase:AppHostingBuild
properties:
project: ${exampleAppHostingBackend.project}
location: ${exampleAppHostingBackend.location}
backend: ${exampleAppHostingBackend.backendId}
buildId: mini-build
source:
container:
image: us-docker.pkg.dev/cloudrun/container/hello
exampleAppHostingBackend:
type: gcp:firebase:AppHostingBackend
name: example
properties:
project: my-project-name
location: us-central1
backendId: mini
appId: 1:0000000000:web:674cde32020e16fbce9dbd
servingLocality: GLOBAL_ACCESS
serviceAccount: ${serviceAccount.email}
options:
dependsOn:
- ${fah}
### Include these blocks only once per project if you are starting from scratch ###
serviceAccount:
type: gcp:serviceaccount:Account
name: service_account
properties:
project: my-project-name
accountId: firebase-app-hosting-compute
displayName: Firebase App Hosting compute service account
createIgnoreAlreadyExists: true
appHostingSaRunner:
type: gcp:projects:IAMMember
name: app_hosting_sa_runner
properties:
project: my-project-name
role: roles/firebaseapphosting.computeRunner
member: ${serviceAccount.member}
fah:
type: gcp:projects:Service
properties:
project: my-project-name
service: firebaseapphosting.googleapis.com
The source property defines where the build comes from. When using source.container, the image property points to a container in Artifact Registry or another registry. The backend and buildId properties link this build to a specific AppHostingBackend resource. This approach lets you deploy known-good images to test your backend configuration before connecting source control.
Add metadata with annotations and labels
Production deployments benefit from metadata that tracks build provenance and organizational ownership.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
//## Include these blocks only once per project if you are starting from scratch ###
const serviceAccount = new gcp.serviceaccount.Account("service_account", {
project: "my-project-name",
accountId: "firebase-app-hosting-compute",
displayName: "Firebase App Hosting compute service account",
createIgnoreAlreadyExists: true,
});
const fah = new gcp.projects.Service("fah", {
project: "my-project-name",
service: "firebaseapphosting.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
project: "my-project-name",
location: "us-central1",
backendId: "full",
appId: "1:0000000000:web:674cde32020e16fbce9dbd",
servingLocality: "GLOBAL_ACCESS",
serviceAccount: serviceAccount.email,
}, {
dependsOn: [fah],
});
const example = new gcp.firebase.AppHostingBuild("example", {
project: exampleAppHostingBackend.project,
location: exampleAppHostingBackend.location,
backend: exampleAppHostingBackend.backendId,
buildId: "full-build",
displayName: "My Build",
annotations: {
key: "value",
},
labels: {
key: "value",
},
source: {
container: {
image: "us-docker.pkg.dev/cloudrun/container/hello",
},
},
});
const appHostingSaRunner = new gcp.projects.IAMMember("app_hosting_sa_runner", {
project: "my-project-name",
role: "roles/firebaseapphosting.computeRunner",
member: serviceAccount.member,
});
import pulumi
import pulumi_gcp as gcp
### Include these blocks only once per project if you are starting from scratch ###
service_account = gcp.serviceaccount.Account("service_account",
project="my-project-name",
account_id="firebase-app-hosting-compute",
display_name="Firebase App Hosting compute service account",
create_ignore_already_exists=True)
fah = gcp.projects.Service("fah",
project="my-project-name",
service="firebaseapphosting.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
project="my-project-name",
location="us-central1",
backend_id="full",
app_id="1:0000000000:web:674cde32020e16fbce9dbd",
serving_locality="GLOBAL_ACCESS",
service_account=service_account.email,
opts = pulumi.ResourceOptions(depends_on=[fah]))
example = gcp.firebase.AppHostingBuild("example",
project=example_app_hosting_backend.project,
location=example_app_hosting_backend.location,
backend=example_app_hosting_backend.backend_id,
build_id="full-build",
display_name="My Build",
annotations={
"key": "value",
},
labels={
"key": "value",
},
source={
"container": {
"image": "us-docker.pkg.dev/cloudrun/container/hello",
},
})
app_hosting_sa_runner = gcp.projects.IAMMember("app_hosting_sa_runner",
project="my-project-name",
role="roles/firebaseapphosting.computeRunner",
member=service_account.member)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// ## Include these blocks only once per project if you are starting from scratch ###
serviceAccount, err := serviceaccount.NewAccount(ctx, "service_account", &serviceaccount.AccountArgs{
Project: pulumi.String("my-project-name"),
AccountId: pulumi.String("firebase-app-hosting-compute"),
DisplayName: pulumi.String("Firebase App Hosting compute service account"),
CreateIgnoreAlreadyExists: pulumi.Bool(true),
})
if err != nil {
return err
}
fah, err := projects.NewService(ctx, "fah", &projects.ServiceArgs{
Project: pulumi.String("my-project-name"),
Service: pulumi.String("firebaseapphosting.googleapis.com"),
})
if err != nil {
return err
}
exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
BackendId: pulumi.String("full"),
AppId: pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
ServingLocality: pulumi.String("GLOBAL_ACCESS"),
ServiceAccount: serviceAccount.Email,
}, pulumi.DependsOn([]pulumi.Resource{
fah,
}))
if err != nil {
return err
}
_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
Project: exampleAppHostingBackend.Project,
Location: exampleAppHostingBackend.Location,
Backend: exampleAppHostingBackend.BackendId,
BuildId: pulumi.String("full-build"),
DisplayName: pulumi.String("My Build"),
Annotations: pulumi.StringMap{
"key": pulumi.String("value"),
},
Labels: pulumi.StringMap{
"key": pulumi.String("value"),
},
Source: &firebase.AppHostingBuildSourceArgs{
Container: &firebase.AppHostingBuildSourceContainerArgs{
Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/hello"),
},
},
})
if err != nil {
return err
}
_, err = projects.NewIAMMember(ctx, "app_hosting_sa_runner", &projects.IAMMemberArgs{
Project: pulumi.String("my-project-name"),
Role: pulumi.String("roles/firebaseapphosting.computeRunner"),
Member: serviceAccount.Member,
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
//## Include these blocks only once per project if you are starting from scratch ###
var serviceAccount = new Gcp.ServiceAccount.Account("service_account", new()
{
Project = "my-project-name",
AccountId = "firebase-app-hosting-compute",
DisplayName = "Firebase App Hosting compute service account",
CreateIgnoreAlreadyExists = true,
});
var fah = new Gcp.Projects.Service("fah", new()
{
Project = "my-project-name",
ServiceName = "firebaseapphosting.googleapis.com",
});
var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
{
Project = "my-project-name",
Location = "us-central1",
BackendId = "full",
AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
ServingLocality = "GLOBAL_ACCESS",
ServiceAccount = serviceAccount.Email,
}, new CustomResourceOptions
{
DependsOn =
{
fah,
},
});
var example = new Gcp.Firebase.AppHostingBuild("example", new()
{
Project = exampleAppHostingBackend.Project,
Location = exampleAppHostingBackend.Location,
Backend = exampleAppHostingBackend.BackendId,
BuildId = "full-build",
DisplayName = "My Build",
Annotations =
{
{ "key", "value" },
},
Labels =
{
{ "key", "value" },
},
Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
{
Container = new Gcp.Firebase.Inputs.AppHostingBuildSourceContainerArgs
{
Image = "us-docker.pkg.dev/cloudrun/container/hello",
},
},
});
var appHostingSaRunner = new Gcp.Projects.IAMMember("app_hosting_sa_runner", new()
{
Project = "my-project-name",
Role = "roles/firebaseapphosting.computeRunner",
Member = serviceAccount.Member,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.projects.Service;
import com.pulumi.gcp.projects.ServiceArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceContainerArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
//## Include these blocks only once per project if you are starting from scratch ###
var serviceAccount = new Account("serviceAccount", AccountArgs.builder()
.project("my-project-name")
.accountId("firebase-app-hosting-compute")
.displayName("Firebase App Hosting compute service account")
.createIgnoreAlreadyExists(true)
.build());
var fah = new Service("fah", ServiceArgs.builder()
.project("my-project-name")
.service("firebaseapphosting.googleapis.com")
.build());
var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
.project("my-project-name")
.location("us-central1")
.backendId("full")
.appId("1:0000000000:web:674cde32020e16fbce9dbd")
.servingLocality("GLOBAL_ACCESS")
.serviceAccount(serviceAccount.email())
.build(), CustomResourceOptions.builder()
.dependsOn(fah)
.build());
var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
.project(exampleAppHostingBackend.project())
.location(exampleAppHostingBackend.location())
.backend(exampleAppHostingBackend.backendId())
.buildId("full-build")
.displayName("My Build")
.annotations(Map.of("key", "value"))
.labels(Map.of("key", "value"))
.source(AppHostingBuildSourceArgs.builder()
.container(AppHostingBuildSourceContainerArgs.builder()
.image("us-docker.pkg.dev/cloudrun/container/hello")
.build())
.build())
.build());
var appHostingSaRunner = new IAMMember("appHostingSaRunner", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/firebaseapphosting.computeRunner")
.member(serviceAccount.member())
.build());
}
}
resources:
example:
type: gcp:firebase:AppHostingBuild
properties:
project: ${exampleAppHostingBackend.project}
location: ${exampleAppHostingBackend.location}
backend: ${exampleAppHostingBackend.backendId}
buildId: full-build
displayName: My Build
annotations:
key: value
labels:
key: value
source:
container:
image: us-docker.pkg.dev/cloudrun/container/hello
exampleAppHostingBackend:
type: gcp:firebase:AppHostingBackend
name: example
properties:
project: my-project-name
location: us-central1
backendId: full
appId: 1:0000000000:web:674cde32020e16fbce9dbd
servingLocality: GLOBAL_ACCESS
serviceAccount: ${serviceAccount.email}
options:
dependsOn:
- ${fah}
### Include these blocks only once per project if you are starting from scratch ###
serviceAccount:
type: gcp:serviceaccount:Account
name: service_account
properties:
project: my-project-name
accountId: firebase-app-hosting-compute
displayName: Firebase App Hosting compute service account
createIgnoreAlreadyExists: true
appHostingSaRunner:
type: gcp:projects:IAMMember
name: app_hosting_sa_runner
properties:
project: my-project-name
role: roles/firebaseapphosting.computeRunner
member: ${serviceAccount.member}
fah:
type: gcp:projects:Service
properties:
project: my-project-name
service: firebaseapphosting.googleapis.com
The displayName property provides a human-readable identifier. The annotations property stores arbitrary metadata that external tools can read but not query. The labels property enables filtering and cost allocation across builds. Both are key-value maps that help you track builds across environments and teams.
Build from a GitHub repository branch
Continuous deployment workflows trigger builds directly from source control, eliminating manual container image management.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const my_repository = new gcp.developerconnect.GitRepositoryLink("my-repository", {
project: "my-project-name",
location: "us-central1",
service: "developerconnect.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
project: "my-project-name",
location: "us-central1",
backendId: "mini",
appId: "1:0000000000:web:674cde32020e16fbce9dbd",
displayName: "My Backend",
servingLocality: "GLOBAL_ACCESS",
serviceAccount: "firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
environment: "prod",
annotations: {
key: "value",
},
labels: {
key: "value",
},
codebase: {
repository: my_repository.name,
rootDirectory: "/",
},
});
const example = new gcp.firebase.AppHostingBuild("example", {
project: exampleAppHostingBackend.project,
location: exampleAppHostingBackend.location,
backend: exampleAppHostingBackend.backendId,
buildId: "gh-build",
source: {
codebase: {
branch: "main",
},
},
});
const devconnect_secret = new gcp.projects.IAMMember("devconnect-secret", {
project: "my-project-name",
role: "roles/secretmanager.admin",
member: devconnect_p4sa.member,
});
//##
//## Include these blocks only once per Github account ###
const my_connection = new gcp.developerconnect.Connection("my-connection", {
project: "my-project-name",
location: "us-central1",
connectionId: "tf-test-connection-new",
githubConfig: {
githubApp: "FIREBASE",
},
}, {
dependsOn: [devconnect_secret],
});
export const nextSteps = my_connection.installationStates;
import pulumi
import pulumi_gcp as gcp
my_repository = gcp.developerconnect.GitRepositoryLink("my-repository",
project="my-project-name",
location="us-central1",
service="developerconnect.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
project="my-project-name",
location="us-central1",
backend_id="mini",
app_id="1:0000000000:web:674cde32020e16fbce9dbd",
display_name="My Backend",
serving_locality="GLOBAL_ACCESS",
service_account="firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
environment="prod",
annotations={
"key": "value",
},
labels={
"key": "value",
},
codebase={
"repository": my_repository.name,
"root_directory": "/",
})
example = gcp.firebase.AppHostingBuild("example",
project=example_app_hosting_backend.project,
location=example_app_hosting_backend.location,
backend=example_app_hosting_backend.backend_id,
build_id="gh-build",
source={
"codebase": {
"branch": "main",
},
})
devconnect_secret = gcp.projects.IAMMember("devconnect-secret",
project="my-project-name",
role="roles/secretmanager.admin",
member=devconnect_p4sa["member"])
###
### Include these blocks only once per Github account ###
my_connection = gcp.developerconnect.Connection("my-connection",
project="my-project-name",
location="us-central1",
connection_id="tf-test-connection-new",
github_config={
"github_app": "FIREBASE",
},
opts = pulumi.ResourceOptions(depends_on=[devconnect_secret]))
pulumi.export("nextSteps", my_connection.installation_states)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/developerconnect"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
my_repository, err := developerconnect.NewGitRepositoryLink(ctx, "my-repository", &developerconnect.GitRepositoryLinkArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
Service: "developerconnect.googleapis.com",
})
if err != nil {
return err
}
exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
BackendId: pulumi.String("mini"),
AppId: pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
DisplayName: pulumi.String("My Backend"),
ServingLocality: pulumi.String("GLOBAL_ACCESS"),
ServiceAccount: pulumi.String("firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com"),
Environment: pulumi.String("prod"),
Annotations: pulumi.StringMap{
"key": pulumi.String("value"),
},
Labels: pulumi.StringMap{
"key": pulumi.String("value"),
},
Codebase: &firebase.AppHostingBackendCodebaseArgs{
Repository: my_repository.Name,
RootDirectory: pulumi.String("/"),
},
})
if err != nil {
return err
}
_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
Project: exampleAppHostingBackend.Project,
Location: exampleAppHostingBackend.Location,
Backend: exampleAppHostingBackend.BackendId,
BuildId: pulumi.String("gh-build"),
Source: &firebase.AppHostingBuildSourceArgs{
Codebase: &firebase.AppHostingBuildSourceCodebaseArgs{
Branch: pulumi.String("main"),
},
},
})
if err != nil {
return err
}
devconnect_secret, err := projects.NewIAMMember(ctx, "devconnect-secret", &projects.IAMMemberArgs{
Project: pulumi.String("my-project-name"),
Role: pulumi.String("roles/secretmanager.admin"),
Member: pulumi.Any(devconnect_p4sa.Member),
})
if err != nil {
return err
}
// ## Include these blocks only once per Github account ###
my_connection, err := developerconnect.NewConnection(ctx, "my-connection", &developerconnect.ConnectionArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
ConnectionId: pulumi.String("tf-test-connection-new"),
GithubConfig: &developerconnect.ConnectionGithubConfigArgs{
GithubApp: pulumi.String("FIREBASE"),
},
}, pulumi.DependsOn([]pulumi.Resource{
devconnect_secret,
}))
if err != nil {
return err
}
ctx.Export("nextSteps", my_connection.InstallationStates)
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var my_repository = new Gcp.DeveloperConnect.GitRepositoryLink("my-repository", new()
{
Project = "my-project-name",
Location = "us-central1",
Service = "developerconnect.googleapis.com",
});
var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
{
Project = "my-project-name",
Location = "us-central1",
BackendId = "mini",
AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
DisplayName = "My Backend",
ServingLocality = "GLOBAL_ACCESS",
ServiceAccount = "firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
Environment = "prod",
Annotations =
{
{ "key", "value" },
},
Labels =
{
{ "key", "value" },
},
Codebase = new Gcp.Firebase.Inputs.AppHostingBackendCodebaseArgs
{
Repository = my_repository.Name,
RootDirectory = "/",
},
});
var example = new Gcp.Firebase.AppHostingBuild("example", new()
{
Project = exampleAppHostingBackend.Project,
Location = exampleAppHostingBackend.Location,
Backend = exampleAppHostingBackend.BackendId,
BuildId = "gh-build",
Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
{
Codebase = new Gcp.Firebase.Inputs.AppHostingBuildSourceCodebaseArgs
{
Branch = "main",
},
},
});
var devconnect_secret = new Gcp.Projects.IAMMember("devconnect-secret", new()
{
Project = "my-project-name",
Role = "roles/secretmanager.admin",
Member = devconnect_p4sa.Member,
});
//##
//## Include these blocks only once per Github account ###
var my_connection = new Gcp.DeveloperConnect.Connection("my-connection", new()
{
Project = "my-project-name",
Location = "us-central1",
ConnectionId = "tf-test-connection-new",
GithubConfig = new Gcp.DeveloperConnect.Inputs.ConnectionGithubConfigArgs
{
GithubApp = "FIREBASE",
},
}, new CustomResourceOptions
{
DependsOn =
{
devconnect_secret,
},
});
return new Dictionary<string, object?>
{
["nextSteps"] = my_connection.InstallationStates,
};
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.developerconnect.GitRepositoryLink;
import com.pulumi.gcp.developerconnect.GitRepositoryLinkArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBackendCodebaseArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceCodebaseArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.gcp.developerconnect.Connection;
import com.pulumi.gcp.developerconnect.ConnectionArgs;
import com.pulumi.gcp.developerconnect.inputs.ConnectionGithubConfigArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var my_repository = new GitRepositoryLink("my-repository", GitRepositoryLinkArgs.builder()
.project("my-project-name")
.location("us-central1")
.service("developerconnect.googleapis.com")
.build());
var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
.project("my-project-name")
.location("us-central1")
.backendId("mini")
.appId("1:0000000000:web:674cde32020e16fbce9dbd")
.displayName("My Backend")
.servingLocality("GLOBAL_ACCESS")
.serviceAccount("firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com")
.environment("prod")
.annotations(Map.of("key", "value"))
.labels(Map.of("key", "value"))
.codebase(AppHostingBackendCodebaseArgs.builder()
.repository(my_repository.name())
.rootDirectory("/")
.build())
.build());
var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
.project(exampleAppHostingBackend.project())
.location(exampleAppHostingBackend.location())
.backend(exampleAppHostingBackend.backendId())
.buildId("gh-build")
.source(AppHostingBuildSourceArgs.builder()
.codebase(AppHostingBuildSourceCodebaseArgs.builder()
.branch("main")
.build())
.build())
.build());
var devconnect_secret = new IAMMember("devconnect-secret", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/secretmanager.admin")
.member(devconnect_p4sa.member())
.build());
//##
//## Include these blocks only once per Github account ###
var my_connection = new Connection("my-connection", ConnectionArgs.builder()
.project("my-project-name")
.location("us-central1")
.connectionId("tf-test-connection-new")
.githubConfig(ConnectionGithubConfigArgs.builder()
.githubApp("FIREBASE")
.build())
.build(), CustomResourceOptions.builder()
.dependsOn(devconnect_secret)
.build());
ctx.export("nextSteps", my_connection.installationStates());
}
}
resources:
example:
type: gcp:firebase:AppHostingBuild
properties:
project: ${exampleAppHostingBackend.project}
location: ${exampleAppHostingBackend.location}
backend: ${exampleAppHostingBackend.backendId}
buildId: gh-build
source:
codebase:
branch: main
exampleAppHostingBackend:
type: gcp:firebase:AppHostingBackend
name: example
properties:
project: my-project-name
location: us-central1
backendId: mini
appId: 1:0000000000:web:674cde32020e16fbce9dbd
displayName: My Backend
servingLocality: GLOBAL_ACCESS
serviceAccount: firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com
environment: prod
annotations:
key: value
labels:
key: value
codebase:
repository: ${["my-repository"].name}
rootDirectory: /
my-repository:
type: gcp:developerconnect:GitRepositoryLink
properties:
project: my-project-name
location: us-central1
service: developerconnect.googleapis.com
devconnect-secret: ###
type: gcp:projects:IAMMember
properties:
project: my-project-name
role: roles/secretmanager.admin
member: ${["devconnect-p4sa"].member}
### Include these blocks only once per Github account ###
my-connection:
type: gcp:developerconnect:Connection
properties:
project: my-project-name
location: us-central1
connectionId: tf-test-connection-new
githubConfig:
githubApp: FIREBASE
options:
dependsOn:
- ${["devconnect-secret"]}
outputs:
nextSteps: ${["my-connection"].installationStates}
When using source.codebase, the branch property specifies which Git branch to build from. The backend must reference a GitRepositoryLink resource that connects to your GitHub repository via Developer Connect. This configuration triggers Cloud Build to clone the repository, build the container image, and deploy it to Cloud Run automatically. The service account needs secretmanager.admin permissions for Developer Connect to access GitHub credentials.
Beyond these examples
These snippets focus on specific build-level features: container image and source code deployment, metadata tagging with annotations and labels, and GitHub integration via Developer Connect. They’re intentionally minimal rather than full deployment pipelines.
The examples rely on pre-existing infrastructure such as Firebase App Hosting backend, service account with firebaseapphosting.computeRunner role, container images in Artifact Registry (for container deployments), and Developer Connect GitHub connection (for source builds). They focus on configuring the build rather than provisioning the surrounding infrastructure.
To keep things focused, common build patterns are omitted, including:
- Build state monitoring and error handling
- Cloud Build logs access (buildLogsUri)
- Build environment configuration
- Multi-environment deployment strategies
These omissions are intentional: the goal is to illustrate how each build feature is wired, not provide drop-in CI/CD modules. See the Firebase App Hosting Build resource reference for all available configuration options.
Let's deploy GCP Firebase App Hosting Builds
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Build Sources & Deployment
source.container.image) or a Github repository (using source.codebase.branch). The source type is immutable after creation.source.container.image to your container image URI, such as us-docker.pkg.dev/cloudrun/container/hello.source.codebase.branch with your branch name (e.g., main), create a Developer Connect GitRepositoryLink, and set the backend’s codebase.repository property. You’ll also need a Developer Connect Connection with githubConfig.githubApp set to FIREBASE.IAM & Permissions
roles/firebaseapphosting.computeRunner role to run App Hosting builds.roles/secretmanager.admin role to access Github credentials.Immutability & Updates
backend, buildId, location, project, source, annotations, and displayName. Changing any of these requires recreating the resource.annotations and labels fields are non-authoritative, meaning they only manage values in your configuration. Use effectiveAnnotations and effectiveLabels to see all values, including those set by other tools.Configuration & Metadata
displayName field has a 63-character limit.annotations store arbitrary metadata (not queryable), while labels are used to organize and categorize resources. Both are non-authoritative fields.Troubleshooting & Build States
BUILDING (in progress), BUILT (image created), DEPLOYING (deploying to Cloud Run), READY (successfully deployed), or FAILED (error occurred).state property for FAILED, then examine errorSource (either CLOUD_BUILD or CLOUD_RUN) and errors for details. Access full logs via the buildLogsUri property.