Deploy GCP Firebase App Hosting Backends

The gcp:firebase/appHostingBackend:AppHostingBackend resource, part of the Pulumi GCP 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 deployment, metadata and environment configuration, and GitHub repository integration for continuous deployment.

Backends require a Firebase Web App, a service account with appropriate IAM roles, and optionally a GitHub repository linked through Developer Connect. The examples are intentionally small. Combine them with your own Firebase project configuration and deployment workflows.

Deploy a backend with minimal configuration

Most deployments start by connecting a Firebase Web App to Cloud Run infrastructure, establishing the serving endpoint and execution identity.

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 becomes the Cloud Run service ID and part of the default domain. The servingLocality property controls whether App Hosting uses regional infrastructure (REGIONAL_STRICT) or global replication (GLOBAL_ACCESS). The serviceAccount must have the firebaseapphosting.computeRunner role to execute the backend. The dependsOn ensures the API is enabled before creating the backend.

Add metadata and environment configuration

Production backends benefit from display names for UI visibility, environment-specific settings, and organizational metadata.

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 appears in Firebase Console. The environment property loads environment-specific configuration at runtime. Annotations store arbitrary metadata that external tools can read, while labels enable resource organization and cost tracking. This example extends the minimal configuration with metadata without changing the core serving behavior.

Connect to a GitHub repository for automatic deployments

Teams using GitHub can configure App Hosting to watch a repository and deploy changes automatically through Developer Connect.

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 links the backend to a GitHub repository through Developer Connect. The Connection resource establishes the GitHub integration using the Firebase GitHub App. The GitRepositoryLink specifies which repository to watch. The rootDirectory property tells App Hosting where to find the application code within the repository. The installationStates output shows whether the GitHub App is installed; you may need to complete installation through the provided URL.

Beyond these examples

These snippets focus on specific backend-level features: backend creation and serving locality, metadata and environment configuration, and GitHub repository integration. They’re intentionally minimal rather than full application deployments.

The examples may reference pre-existing infrastructure such as Firebase Web Apps (appId references), GitHub repositories, and GCP projects with billing enabled. They focus on configuring the backend rather than provisioning everything around it.

To keep things focused, common backend patterns are omitted, including:

  • IAM role bindings for service accounts (shown but not explained)
  • Cloud Run service configuration (managed automatically)
  • Custom domain mapping
  • Build configuration and environment variables

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?
The service account requires roles/firebaseapphosting.computeRunner. For GitHub integration, also grant roles/developerconnect.readTokenAccessor and 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 enabled before creating the backend.
Should I create the service account once per project or per backend?
Once per project. The examples include comments stating “Include these blocks only once per project if you are starting from scratch.”
Configuration & Immutability
What properties can't be changed after creation?
The backendId, location, project, and servingLocality properties are immutable and cannot be modified after the backend is created.
What's the difference between REGIONAL_STRICT and GLOBAL_ACCESS?
REGIONAL_STRICT contains the backend to a single region, while GLOBAL_ACCESS uses App Hosting’s global-replicated serving infrastructure.
What does backendId control?
The backendId is used as the service ID for Cloud Run and as part of the default domain name for your backend.
Source Code Integration
How do I connect my backend to a GitHub repository?
Configure the codebase property with a repository reference. You’ll need to first create a developerconnect.Connection and GitRepositoryLink as shown in the GitHub example.
What does the installationStates output mean in the GitHub example?
The installationStates from the connection shows the GitHub App installation status, exported as nextSteps to guide you through completing the GitHub integration.
Metadata & Labels
How do annotations and labels work with this resource?
The annotations and labels fields are non-authoritative, meaning they only manage values in your configuration. Use effectiveAnnotations and effectiveLabels to see all annotations and labels present on the resource, including those set by other clients.
What are the managed resources shown in the output?
The managedResources output lists the resources (like Cloud Run services) that App Hosting automatically creates and manages for this backend.

Using a different cloud?

Explore serverless guides for other cloud providers: