The gcp:firebase/appHostingBackend:AppHostingBackend resource, part of the Pulumi Google Cloud provider, defines a Firebase App Hosting backend that connects a Firebase Web App to Cloud Run infrastructure for serving web applications. This guide focuses on three capabilities: minimal backend creation with global serving, metadata and environment configuration, and GitHub repository integration for automatic deployments.
Backends require a Firebase Web App, a service account with appropriate IAM roles, and enabled Google Cloud APIs. GitHub integration requires Developer Connect setup and the Firebase GitHub App. The examples are intentionally small. Combine them with your own IAM configuration, API enablement, and deployment workflows.
Create a backend with minimal configuration
Most deployments start by connecting a Firebase Web App to Cloud Run infrastructure, establishing the foundation for serving applications through Firebase’s global network.
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 example = 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 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 = 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]))
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
}
_, 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 = 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 example = 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 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.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 example = new AppHostingBackend("example", 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 appHostingSaRunner = new IAMMember("appHostingSaRunner", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/firebaseapphosting.computeRunner")
.member(serviceAccount.member())
.build());
}
}
resources:
example:
type: gcp:firebase:AppHostingBackend
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 backendId serves as both the backend identifier and the Cloud Run service ID. The appId links this backend to a specific Firebase Web App. The servingLocality property controls whether content is served from a single region (REGIONAL_STRICT) or through Firebase’s global infrastructure (GLOBAL_ACCESS). The serviceAccount must have the roles/firebaseapphosting.computeRunner role to execute Cloud Run services.
Add metadata and environment configuration
Production backends often need human-readable names, environment-specific settings, and organizational tags.
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 example = new gcp.firebase.AppHostingBackend("example", {
project: "my-project-name",
location: "us-central1",
backendId: "full",
appId: "1:0000000000:web:674cde32020e16fbce9dbd",
displayName: "My Backend",
servingLocality: "GLOBAL_ACCESS",
serviceAccount: serviceAccount.email,
environment: "prod",
annotations: {
key: "value",
},
labels: {
key: "value",
},
}, {
dependsOn: [fah],
});
const appHostingSaDeveloperconnect = new gcp.projects.IAMMember("app_hosting_sa_developerconnect", {
project: "my-project-name",
role: "roles/developerconnect.readTokenAccessor",
member: serviceAccount.member,
});
const appHostingSaAdminsdk = new gcp.projects.IAMMember("app_hosting_sa_adminsdk", {
project: "my-project-name",
role: "roles/firebase.sdkAdminServiceAgent",
member: serviceAccount.member,
});
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 = gcp.firebase.AppHostingBackend("example",
project="my-project-name",
location="us-central1",
backend_id="full",
app_id="1:0000000000:web:674cde32020e16fbce9dbd",
display_name="My Backend",
serving_locality="GLOBAL_ACCESS",
service_account=service_account.email,
environment="prod",
annotations={
"key": "value",
},
labels={
"key": "value",
},
opts = pulumi.ResourceOptions(depends_on=[fah]))
app_hosting_sa_developerconnect = gcp.projects.IAMMember("app_hosting_sa_developerconnect",
project="my-project-name",
role="roles/developerconnect.readTokenAccessor",
member=service_account.member)
app_hosting_sa_adminsdk = gcp.projects.IAMMember("app_hosting_sa_adminsdk",
project="my-project-name",
role="roles/firebase.sdkAdminServiceAgent",
member=service_account.member)
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
}
_, 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"),
DisplayName: pulumi.String("My Backend"),
ServingLocality: pulumi.String("GLOBAL_ACCESS"),
ServiceAccount: serviceAccount.Email,
Environment: pulumi.String("prod"),
Annotations: pulumi.StringMap{
"key": pulumi.String("value"),
},
Labels: pulumi.StringMap{
"key": pulumi.String("value"),
},
}, pulumi.DependsOn([]pulumi.Resource{
fah,
}))
if err != nil {
return err
}
_, err = projects.NewIAMMember(ctx, "app_hosting_sa_developerconnect", &projects.IAMMemberArgs{
Project: pulumi.String("my-project-name"),
Role: pulumi.String("roles/developerconnect.readTokenAccessor"),
Member: serviceAccount.Member,
})
if err != nil {
return err
}
_, err = projects.NewIAMMember(ctx, "app_hosting_sa_adminsdk", &projects.IAMMemberArgs{
Project: pulumi.String("my-project-name"),
Role: pulumi.String("roles/firebase.sdkAdminServiceAgent"),
Member: serviceAccount.Member,
})
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 example = new Gcp.Firebase.AppHostingBackend("example", new()
{
Project = "my-project-name",
Location = "us-central1",
BackendId = "full",
AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
DisplayName = "My Backend",
ServingLocality = "GLOBAL_ACCESS",
ServiceAccount = serviceAccount.Email,
Environment = "prod",
Annotations =
{
{ "key", "value" },
},
Labels =
{
{ "key", "value" },
},
}, new CustomResourceOptions
{
DependsOn =
{
fah,
},
});
var appHostingSaDeveloperconnect = new Gcp.Projects.IAMMember("app_hosting_sa_developerconnect", new()
{
Project = "my-project-name",
Role = "roles/developerconnect.readTokenAccessor",
Member = serviceAccount.Member,
});
var appHostingSaAdminsdk = new Gcp.Projects.IAMMember("app_hosting_sa_adminsdk", new()
{
Project = "my-project-name",
Role = "roles/firebase.sdkAdminServiceAgent",
Member = serviceAccount.Member,
});
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.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 example = new AppHostingBackend("example", AppHostingBackendArgs.builder()
.project("my-project-name")
.location("us-central1")
.backendId("full")
.appId("1:0000000000:web:674cde32020e16fbce9dbd")
.displayName("My Backend")
.servingLocality("GLOBAL_ACCESS")
.serviceAccount(serviceAccount.email())
.environment("prod")
.annotations(Map.of("key", "value"))
.labels(Map.of("key", "value"))
.build(), CustomResourceOptions.builder()
.dependsOn(fah)
.build());
var appHostingSaDeveloperconnect = new IAMMember("appHostingSaDeveloperconnect", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/developerconnect.readTokenAccessor")
.member(serviceAccount.member())
.build());
var appHostingSaAdminsdk = new IAMMember("appHostingSaAdminsdk", IAMMemberArgs.builder()
.project("my-project-name")
.role("roles/firebase.sdkAdminServiceAgent")
.member(serviceAccount.member())
.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:AppHostingBackend
properties:
project: my-project-name
location: us-central1
backendId: full
appId: 1:0000000000:web:674cde32020e16fbce9dbd
displayName: My Backend
servingLocality: GLOBAL_ACCESS
serviceAccount: ${serviceAccount.email}
environment: prod
annotations:
key: value
labels:
key: value
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
appHostingSaDeveloperconnect:
type: gcp:projects:IAMMember
name: app_hosting_sa_developerconnect
properties:
project: my-project-name
role: roles/developerconnect.readTokenAccessor
member: ${serviceAccount.member}
appHostingSaAdminsdk:
type: gcp:projects:IAMMember
name: app_hosting_sa_adminsdk
properties:
project: my-project-name
role: roles/firebase.sdkAdminServiceAgent
member: ${serviceAccount.member}
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 provides a readable identifier (63 character limit). The environment property loads environment-specific configuration variables. The annotations and labels properties add metadata for organization and cost tracking; annotations are non-queryable and preserved during updates, while labels support filtering and organization.
Connect to a GitHub repository for automatic deployments
Teams using GitHub can configure App Hosting to watch a repository and deploy changes automatically when code is pushed.
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 devconnect_p4sa = new gcp.projects.ServiceIdentity("devconnect-p4sa", {
project: "my-project-name",
service: "developerconnect.googleapis.com",
});
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],
});
const my_repository = new gcp.developerconnect.GitRepositoryLink("my-repository", {
project: "my-project-name",
location: "us-central1",
gitRepositoryLinkId: "my-repo",
parentConnection: my_connection.connectionId,
cloneUri: "https://github.com/myuser/myrepo.git",
});
const example = new gcp.firebase.AppHostingBackend("example", {
project: "my-project-name",
location: "us-central1",
backendId: "my-backend-gh",
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: "/",
},
});
export const nextSteps = my_connection.installationStates;
import pulumi
import pulumi_gcp as gcp
### Include these blocks only once per project if you are starting from scratch ###
devconnect_p4sa = gcp.projects.ServiceIdentity("devconnect-p4sa",
project="my-project-name",
service="developerconnect.googleapis.com")
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]))
my_repository = gcp.developerconnect.GitRepositoryLink("my-repository",
project="my-project-name",
location="us-central1",
git_repository_link_id="my-repo",
parent_connection=my_connection.connection_id,
clone_uri="https://github.com/myuser/myrepo.git")
example = gcp.firebase.AppHostingBackend("example",
project="my-project-name",
location="us-central1",
backend_id="my-backend-gh",
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": "/",
})
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 {
// ## Include these blocks only once per project if you are starting from scratch ###
devconnect_p4sa, err := projects.NewServiceIdentity(ctx, "devconnect-p4sa", &projects.ServiceIdentityArgs{
Project: pulumi.String("my-project-name"),
Service: pulumi.String("developerconnect.googleapis.com"),
})
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: 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
}
my_repository, err := developerconnect.NewGitRepositoryLink(ctx, "my-repository", &developerconnect.GitRepositoryLinkArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
GitRepositoryLinkId: pulumi.String("my-repo"),
ParentConnection: my_connection.ConnectionId,
CloneUri: pulumi.String("https://github.com/myuser/myrepo.git"),
})
if err != nil {
return err
}
_, err = firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
Project: pulumi.String("my-project-name"),
Location: pulumi.String("us-central1"),
BackendId: pulumi.String("my-backend-gh"),
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
}
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(() =>
{
//## Include these blocks only once per project if you are starting from scratch ###
var devconnect_p4sa = new Gcp.Projects.ServiceIdentity("devconnect-p4sa", new()
{
Project = "my-project-name",
Service = "developerconnect.googleapis.com",
});
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,
},
});
var my_repository = new Gcp.DeveloperConnect.GitRepositoryLink("my-repository", new()
{
Project = "my-project-name",
Location = "us-central1",
GitRepositoryLinkId = "my-repo",
ParentConnection = my_connection.ConnectionId,
CloneUri = "https://github.com/myuser/myrepo.git",
});
var example = new Gcp.Firebase.AppHostingBackend("example", new()
{
Project = "my-project-name",
Location = "us-central1",
BackendId = "my-backend-gh",
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 = "/",
},
});
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.projects.ServiceIdentity;
import com.pulumi.gcp.projects.ServiceIdentityArgs;
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.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.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 devconnect_p4sa = new ServiceIdentity("devconnect-p4sa", ServiceIdentityArgs.builder()
.project("my-project-name")
.service("developerconnect.googleapis.com")
.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());
var my_repository = new GitRepositoryLink("my-repository", GitRepositoryLinkArgs.builder()
.project("my-project-name")
.location("us-central1")
.gitRepositoryLinkId("my-repo")
.parentConnection(my_connection.connectionId())
.cloneUri("https://github.com/myuser/myrepo.git")
.build());
var example = new AppHostingBackend("example", AppHostingBackendArgs.builder()
.project("my-project-name")
.location("us-central1")
.backendId("my-backend-gh")
.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());
ctx.export("nextSteps", my_connection.installationStates());
}
}
resources:
example:
type: gcp:firebase:AppHostingBackend
properties:
project: my-project-name
location: us-central1
backendId: my-backend-gh
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
gitRepositoryLinkId: my-repo
parentConnection: ${["my-connection"].connectionId}
cloneUri: https://github.com/myuser/myrepo.git
### Include these blocks only once per project if you are starting from scratch ###
devconnect-p4sa:
type: gcp:projects:ServiceIdentity
properties:
project: my-project-name
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}
The codebase property connects the backend to a GitHub repository through Developer Connect. The repository field references a GitRepositoryLink resource that points to your GitHub repository. The rootDirectory specifies where App Hosting should look for your application code (use “/” for the repository root). Developer Connect requires the Firebase GitHub App to be installed on your GitHub account and the service identity needs secretmanager.admin permissions to manage GitHub credentials.
Beyond these examples
These snippets focus on specific backend-level features: backend creation and serving configuration, metadata and environment management, and GitHub repository integration. They’re intentionally minimal rather than full deployment pipelines.
The examples reference pre-existing infrastructure such as Firebase Web Apps (appId references), service accounts with appropriate IAM roles, enabled Google Cloud APIs (firebaseapphosting, developerconnect), and GitHub repositories with the Firebase GitHub App installed. They focus on configuring the backend rather than provisioning the surrounding infrastructure.
To keep things focused, common App Hosting patterns are omitted, including:
- Service account creation and IAM role assignment (shown in comments but not resource focus)
- API enablement (shown as dependency but not configuration focus)
- Build configuration and deployment triggers
- Custom domain mapping and SSL certificates
- Rollback and traffic splitting strategies
- Monitoring and logging configuration
These omissions are intentional: the goal is to illustrate how each backend feature is wired, not provide drop-in hosting modules. See the Firebase App Hosting Backend resource reference for all available configuration options.
Let's deploy GCP Firebase App Hosting Backends
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Setup & Prerequisites
roles/firebaseapphosting.computeRunner. For GitHub integration, add roles/developerconnect.readTokenAccessor. For full functionality, also include roles/firebase.sdkAdminServiceAgent.firebaseapphosting.googleapis.com and use dependsOn to ensure the API is active before creating the backend.Configuration & Immutability
backendId, location, project, and servingLocality. Changing any of these requires recreating the backend.backendId serves multiple purposes: it’s used as the service ID for Cloud Run and as part of the default domain name for your backend.REGIONAL_STRICT contains your backend to a single region, while GLOBAL_ACCESS uses App Hosting’s global-replicated serving infrastructure for worldwide distribution.GitHub Integration
codebase property with a repository link. First, create a Developer Connect connection and git repository link, then reference the repository link in your backend’s codebase configuration.roles/secretmanager.admin, create a connection with githubApp: FIREBASE, and set up a git repository link. These resources are created once per GitHub account.Metadata & Labels
annotations and labels fields are non-authoritative and only manage values in your configuration. Use effectiveAnnotations and effectiveLabels to access all annotations and labels on the resource, including those set by external tools.environment property specifies an environment name used to load environment variables from environment-specific configuration for your backend.Using a different cloud?
Explore serverless guides for other cloud providers: