Deploy GCP Firebase App Hosting Backends

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 FREE

Frequently Asked Questions

Setup & Prerequisites
What IAM roles does the service account need?
At minimum, the service account needs roles/firebaseapphosting.computeRunner. For GitHub integration, add roles/developerconnect.readTokenAccessor. For full functionality, also include roles/firebase.sdkAdminServiceAgent.
Do I need to enable any APIs before creating a backend?
Yes, enable firebaseapphosting.googleapis.com and use dependsOn to ensure the API is active before creating the backend.
Can I reuse the same service account for multiple backends?
Yes, the service account can be shared across backends. The examples note to create the service account and IAM bindings only once per project.
Configuration & Immutability
What properties can't I change after creating a backend?
Four properties are immutable: backendId, location, project, and servingLocality. Changing any of these requires recreating the backend.
What's the purpose of backendId?
The 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.
What's the difference between REGIONAL_STRICT and GLOBAL_ACCESS?
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
How do I connect my backend to a GitHub repository?
Set the 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.
What additional setup is needed for GitHub integration?
You’ll need to create a service identity for Developer Connect, grant it 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
Why should I use effectiveAnnotations and effectiveLabels instead of annotations and labels?
The 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.
What's the environment property used for?
The 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: