Deploy GCP Firebase App Hosting Builds

The gcp:firebase/appHostingBuild:AppHostingBuild resource, part of the Pulumi GCP provider, represents a single build for a Firebase App Hosting backend. Each build encapsulates an Artifact Registry container image, a Cloud Build invocation, and a Cloud Run revision at a specific point in time. This guide focuses on three capabilities: deploying pre-built container images, adding metadata with annotations and labels, and building from GitHub source code.

Builds depend on an existing AppHostingBackend resource, a service account with the firebaseapphosting.computeRunner role, and either a container image in Artifact Registry or a GitHub repository connection via Developer Connect. The examples are intentionally small. Combine them with your own backend configuration and deployment workflows.

Deploy a pre-built container image

Teams often validate their backend configuration by deploying existing container images before setting up automated builds from source.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

//## Include these blocks only once per project if you are starting from scratch ###
const serviceAccount = new gcp.serviceaccount.Account("service_account", {
    project: "my-project-name",
    accountId: "firebase-app-hosting-compute",
    displayName: "Firebase App Hosting compute service account",
    createIgnoreAlreadyExists: true,
});
const fah = new gcp.projects.Service("fah", {
    project: "my-project-name",
    service: "firebaseapphosting.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
    project: "my-project-name",
    location: "us-central1",
    backendId: "mini",
    appId: "1:0000000000:web:674cde32020e16fbce9dbd",
    servingLocality: "GLOBAL_ACCESS",
    serviceAccount: serviceAccount.email,
}, {
    dependsOn: [fah],
});
const example = new gcp.firebase.AppHostingBuild("example", {
    project: exampleAppHostingBackend.project,
    location: exampleAppHostingBackend.location,
    backend: exampleAppHostingBackend.backendId,
    buildId: "mini-build",
    source: {
        container: {
            image: "us-docker.pkg.dev/cloudrun/container/hello",
        },
    },
});
const appHostingSaRunner = new gcp.projects.IAMMember("app_hosting_sa_runner", {
    project: "my-project-name",
    role: "roles/firebaseapphosting.computeRunner",
    member: serviceAccount.member,
});
import pulumi
import pulumi_gcp as gcp

### Include these blocks only once per project if you are starting from scratch ###
service_account = gcp.serviceaccount.Account("service_account",
    project="my-project-name",
    account_id="firebase-app-hosting-compute",
    display_name="Firebase App Hosting compute service account",
    create_ignore_already_exists=True)
fah = gcp.projects.Service("fah",
    project="my-project-name",
    service="firebaseapphosting.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
    project="my-project-name",
    location="us-central1",
    backend_id="mini",
    app_id="1:0000000000:web:674cde32020e16fbce9dbd",
    serving_locality="GLOBAL_ACCESS",
    service_account=service_account.email,
    opts = pulumi.ResourceOptions(depends_on=[fah]))
example = gcp.firebase.AppHostingBuild("example",
    project=example_app_hosting_backend.project,
    location=example_app_hosting_backend.location,
    backend=example_app_hosting_backend.backend_id,
    build_id="mini-build",
    source={
        "container": {
            "image": "us-docker.pkg.dev/cloudrun/container/hello",
        },
    })
app_hosting_sa_runner = gcp.projects.IAMMember("app_hosting_sa_runner",
    project="my-project-name",
    role="roles/firebaseapphosting.computeRunner",
    member=service_account.member)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ## Include these blocks only once per project if you are starting from scratch ###
		serviceAccount, err := serviceaccount.NewAccount(ctx, "service_account", &serviceaccount.AccountArgs{
			Project:                   pulumi.String("my-project-name"),
			AccountId:                 pulumi.String("firebase-app-hosting-compute"),
			DisplayName:               pulumi.String("Firebase App Hosting compute service account"),
			CreateIgnoreAlreadyExists: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		fah, err := projects.NewService(ctx, "fah", &projects.ServiceArgs{
			Project: pulumi.String("my-project-name"),
			Service: pulumi.String("firebaseapphosting.googleapis.com"),
		})
		if err != nil {
			return err
		}
		exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
			Project:         pulumi.String("my-project-name"),
			Location:        pulumi.String("us-central1"),
			BackendId:       pulumi.String("mini"),
			AppId:           pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
			ServingLocality: pulumi.String("GLOBAL_ACCESS"),
			ServiceAccount:  serviceAccount.Email,
		}, pulumi.DependsOn([]pulumi.Resource{
			fah,
		}))
		if err != nil {
			return err
		}
		_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
			Project:  exampleAppHostingBackend.Project,
			Location: exampleAppHostingBackend.Location,
			Backend:  exampleAppHostingBackend.BackendId,
			BuildId:  pulumi.String("mini-build"),
			Source: &firebase.AppHostingBuildSourceArgs{
				Container: &firebase.AppHostingBuildSourceContainerArgs{
					Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/hello"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = projects.NewIAMMember(ctx, "app_hosting_sa_runner", &projects.IAMMemberArgs{
			Project: pulumi.String("my-project-name"),
			Role:    pulumi.String("roles/firebaseapphosting.computeRunner"),
			Member:  serviceAccount.Member,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    //## Include these blocks only once per project if you are starting from scratch ###
    var serviceAccount = new Gcp.ServiceAccount.Account("service_account", new()
    {
        Project = "my-project-name",
        AccountId = "firebase-app-hosting-compute",
        DisplayName = "Firebase App Hosting compute service account",
        CreateIgnoreAlreadyExists = true,
    });

    var fah = new Gcp.Projects.Service("fah", new()
    {
        Project = "my-project-name",
        ServiceName = "firebaseapphosting.googleapis.com",
    });

    var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
    {
        Project = "my-project-name",
        Location = "us-central1",
        BackendId = "mini",
        AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
        ServingLocality = "GLOBAL_ACCESS",
        ServiceAccount = serviceAccount.Email,
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            fah,
        },
    });

    var example = new Gcp.Firebase.AppHostingBuild("example", new()
    {
        Project = exampleAppHostingBackend.Project,
        Location = exampleAppHostingBackend.Location,
        Backend = exampleAppHostingBackend.BackendId,
        BuildId = "mini-build",
        Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
        {
            Container = new Gcp.Firebase.Inputs.AppHostingBuildSourceContainerArgs
            {
                Image = "us-docker.pkg.dev/cloudrun/container/hello",
            },
        },
    });

    var appHostingSaRunner = new Gcp.Projects.IAMMember("app_hosting_sa_runner", new()
    {
        Project = "my-project-name",
        Role = "roles/firebaseapphosting.computeRunner",
        Member = serviceAccount.Member,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.projects.Service;
import com.pulumi.gcp.projects.ServiceArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceContainerArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        //## Include these blocks only once per project if you are starting from scratch ###
        var serviceAccount = new Account("serviceAccount", AccountArgs.builder()
            .project("my-project-name")
            .accountId("firebase-app-hosting-compute")
            .displayName("Firebase App Hosting compute service account")
            .createIgnoreAlreadyExists(true)
            .build());

        var fah = new Service("fah", ServiceArgs.builder()
            .project("my-project-name")
            .service("firebaseapphosting.googleapis.com")
            .build());

        var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
            .project("my-project-name")
            .location("us-central1")
            .backendId("mini")
            .appId("1:0000000000:web:674cde32020e16fbce9dbd")
            .servingLocality("GLOBAL_ACCESS")
            .serviceAccount(serviceAccount.email())
            .build(), CustomResourceOptions.builder()
                .dependsOn(fah)
                .build());

        var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
            .project(exampleAppHostingBackend.project())
            .location(exampleAppHostingBackend.location())
            .backend(exampleAppHostingBackend.backendId())
            .buildId("mini-build")
            .source(AppHostingBuildSourceArgs.builder()
                .container(AppHostingBuildSourceContainerArgs.builder()
                    .image("us-docker.pkg.dev/cloudrun/container/hello")
                    .build())
                .build())
            .build());

        var appHostingSaRunner = new IAMMember("appHostingSaRunner", IAMMemberArgs.builder()
            .project("my-project-name")
            .role("roles/firebaseapphosting.computeRunner")
            .member(serviceAccount.member())
            .build());

    }
}
resources:
  example:
    type: gcp:firebase:AppHostingBuild
    properties:
      project: ${exampleAppHostingBackend.project}
      location: ${exampleAppHostingBackend.location}
      backend: ${exampleAppHostingBackend.backendId}
      buildId: mini-build
      source:
        container:
          image: us-docker.pkg.dev/cloudrun/container/hello
  exampleAppHostingBackend:
    type: gcp:firebase:AppHostingBackend
    name: example
    properties:
      project: my-project-name
      location: us-central1
      backendId: mini
      appId: 1:0000000000:web:674cde32020e16fbce9dbd
      servingLocality: GLOBAL_ACCESS
      serviceAccount: ${serviceAccount.email}
    options:
      dependsOn:
        - ${fah}
  ### Include these blocks only once per project if you are starting from scratch ###
  serviceAccount:
    type: gcp:serviceaccount:Account
    name: service_account
    properties:
      project: my-project-name
      accountId: firebase-app-hosting-compute
      displayName: Firebase App Hosting compute service account
      createIgnoreAlreadyExists: true
  appHostingSaRunner:
    type: gcp:projects:IAMMember
    name: app_hosting_sa_runner
    properties:
      project: my-project-name
      role: roles/firebaseapphosting.computeRunner
      member: ${serviceAccount.member}
  fah:
    type: gcp:projects:Service
    properties:
      project: my-project-name
      service: firebaseapphosting.googleapis.com

The source property defines where the build comes from. When using source.container, the image property points to a container in Artifact Registry or another registry. The backend and buildId properties link this build to a specific AppHostingBackend resource. This approach lets you deploy known-good images to test your backend configuration before connecting source control.

Add metadata with annotations and labels

Production deployments benefit from metadata that tracks build provenance and organizational ownership.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

//## Include these blocks only once per project if you are starting from scratch ###
const serviceAccount = new gcp.serviceaccount.Account("service_account", {
    project: "my-project-name",
    accountId: "firebase-app-hosting-compute",
    displayName: "Firebase App Hosting compute service account",
    createIgnoreAlreadyExists: true,
});
const fah = new gcp.projects.Service("fah", {
    project: "my-project-name",
    service: "firebaseapphosting.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
    project: "my-project-name",
    location: "us-central1",
    backendId: "full",
    appId: "1:0000000000:web:674cde32020e16fbce9dbd",
    servingLocality: "GLOBAL_ACCESS",
    serviceAccount: serviceAccount.email,
}, {
    dependsOn: [fah],
});
const example = new gcp.firebase.AppHostingBuild("example", {
    project: exampleAppHostingBackend.project,
    location: exampleAppHostingBackend.location,
    backend: exampleAppHostingBackend.backendId,
    buildId: "full-build",
    displayName: "My Build",
    annotations: {
        key: "value",
    },
    labels: {
        key: "value",
    },
    source: {
        container: {
            image: "us-docker.pkg.dev/cloudrun/container/hello",
        },
    },
});
const appHostingSaRunner = new gcp.projects.IAMMember("app_hosting_sa_runner", {
    project: "my-project-name",
    role: "roles/firebaseapphosting.computeRunner",
    member: serviceAccount.member,
});
import pulumi
import pulumi_gcp as gcp

### Include these blocks only once per project if you are starting from scratch ###
service_account = gcp.serviceaccount.Account("service_account",
    project="my-project-name",
    account_id="firebase-app-hosting-compute",
    display_name="Firebase App Hosting compute service account",
    create_ignore_already_exists=True)
fah = gcp.projects.Service("fah",
    project="my-project-name",
    service="firebaseapphosting.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
    project="my-project-name",
    location="us-central1",
    backend_id="full",
    app_id="1:0000000000:web:674cde32020e16fbce9dbd",
    serving_locality="GLOBAL_ACCESS",
    service_account=service_account.email,
    opts = pulumi.ResourceOptions(depends_on=[fah]))
example = gcp.firebase.AppHostingBuild("example",
    project=example_app_hosting_backend.project,
    location=example_app_hosting_backend.location,
    backend=example_app_hosting_backend.backend_id,
    build_id="full-build",
    display_name="My Build",
    annotations={
        "key": "value",
    },
    labels={
        "key": "value",
    },
    source={
        "container": {
            "image": "us-docker.pkg.dev/cloudrun/container/hello",
        },
    })
app_hosting_sa_runner = gcp.projects.IAMMember("app_hosting_sa_runner",
    project="my-project-name",
    role="roles/firebaseapphosting.computeRunner",
    member=service_account.member)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ## Include these blocks only once per project if you are starting from scratch ###
		serviceAccount, err := serviceaccount.NewAccount(ctx, "service_account", &serviceaccount.AccountArgs{
			Project:                   pulumi.String("my-project-name"),
			AccountId:                 pulumi.String("firebase-app-hosting-compute"),
			DisplayName:               pulumi.String("Firebase App Hosting compute service account"),
			CreateIgnoreAlreadyExists: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		fah, err := projects.NewService(ctx, "fah", &projects.ServiceArgs{
			Project: pulumi.String("my-project-name"),
			Service: pulumi.String("firebaseapphosting.googleapis.com"),
		})
		if err != nil {
			return err
		}
		exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
			Project:         pulumi.String("my-project-name"),
			Location:        pulumi.String("us-central1"),
			BackendId:       pulumi.String("full"),
			AppId:           pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
			ServingLocality: pulumi.String("GLOBAL_ACCESS"),
			ServiceAccount:  serviceAccount.Email,
		}, pulumi.DependsOn([]pulumi.Resource{
			fah,
		}))
		if err != nil {
			return err
		}
		_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
			Project:     exampleAppHostingBackend.Project,
			Location:    exampleAppHostingBackend.Location,
			Backend:     exampleAppHostingBackend.BackendId,
			BuildId:     pulumi.String("full-build"),
			DisplayName: pulumi.String("My Build"),
			Annotations: pulumi.StringMap{
				"key": pulumi.String("value"),
			},
			Labels: pulumi.StringMap{
				"key": pulumi.String("value"),
			},
			Source: &firebase.AppHostingBuildSourceArgs{
				Container: &firebase.AppHostingBuildSourceContainerArgs{
					Image: pulumi.String("us-docker.pkg.dev/cloudrun/container/hello"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = projects.NewIAMMember(ctx, "app_hosting_sa_runner", &projects.IAMMemberArgs{
			Project: pulumi.String("my-project-name"),
			Role:    pulumi.String("roles/firebaseapphosting.computeRunner"),
			Member:  serviceAccount.Member,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    //## Include these blocks only once per project if you are starting from scratch ###
    var serviceAccount = new Gcp.ServiceAccount.Account("service_account", new()
    {
        Project = "my-project-name",
        AccountId = "firebase-app-hosting-compute",
        DisplayName = "Firebase App Hosting compute service account",
        CreateIgnoreAlreadyExists = true,
    });

    var fah = new Gcp.Projects.Service("fah", new()
    {
        Project = "my-project-name",
        ServiceName = "firebaseapphosting.googleapis.com",
    });

    var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
    {
        Project = "my-project-name",
        Location = "us-central1",
        BackendId = "full",
        AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
        ServingLocality = "GLOBAL_ACCESS",
        ServiceAccount = serviceAccount.Email,
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            fah,
        },
    });

    var example = new Gcp.Firebase.AppHostingBuild("example", new()
    {
        Project = exampleAppHostingBackend.Project,
        Location = exampleAppHostingBackend.Location,
        Backend = exampleAppHostingBackend.BackendId,
        BuildId = "full-build",
        DisplayName = "My Build",
        Annotations = 
        {
            { "key", "value" },
        },
        Labels = 
        {
            { "key", "value" },
        },
        Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
        {
            Container = new Gcp.Firebase.Inputs.AppHostingBuildSourceContainerArgs
            {
                Image = "us-docker.pkg.dev/cloudrun/container/hello",
            },
        },
    });

    var appHostingSaRunner = new Gcp.Projects.IAMMember("app_hosting_sa_runner", new()
    {
        Project = "my-project-name",
        Role = "roles/firebaseapphosting.computeRunner",
        Member = serviceAccount.Member,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.projects.Service;
import com.pulumi.gcp.projects.ServiceArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceContainerArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        //## Include these blocks only once per project if you are starting from scratch ###
        var serviceAccount = new Account("serviceAccount", AccountArgs.builder()
            .project("my-project-name")
            .accountId("firebase-app-hosting-compute")
            .displayName("Firebase App Hosting compute service account")
            .createIgnoreAlreadyExists(true)
            .build());

        var fah = new Service("fah", ServiceArgs.builder()
            .project("my-project-name")
            .service("firebaseapphosting.googleapis.com")
            .build());

        var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
            .project("my-project-name")
            .location("us-central1")
            .backendId("full")
            .appId("1:0000000000:web:674cde32020e16fbce9dbd")
            .servingLocality("GLOBAL_ACCESS")
            .serviceAccount(serviceAccount.email())
            .build(), CustomResourceOptions.builder()
                .dependsOn(fah)
                .build());

        var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
            .project(exampleAppHostingBackend.project())
            .location(exampleAppHostingBackend.location())
            .backend(exampleAppHostingBackend.backendId())
            .buildId("full-build")
            .displayName("My Build")
            .annotations(Map.of("key", "value"))
            .labels(Map.of("key", "value"))
            .source(AppHostingBuildSourceArgs.builder()
                .container(AppHostingBuildSourceContainerArgs.builder()
                    .image("us-docker.pkg.dev/cloudrun/container/hello")
                    .build())
                .build())
            .build());

        var appHostingSaRunner = new IAMMember("appHostingSaRunner", IAMMemberArgs.builder()
            .project("my-project-name")
            .role("roles/firebaseapphosting.computeRunner")
            .member(serviceAccount.member())
            .build());

    }
}
resources:
  example:
    type: gcp:firebase:AppHostingBuild
    properties:
      project: ${exampleAppHostingBackend.project}
      location: ${exampleAppHostingBackend.location}
      backend: ${exampleAppHostingBackend.backendId}
      buildId: full-build
      displayName: My Build
      annotations:
        key: value
      labels:
        key: value
      source:
        container:
          image: us-docker.pkg.dev/cloudrun/container/hello
  exampleAppHostingBackend:
    type: gcp:firebase:AppHostingBackend
    name: example
    properties:
      project: my-project-name
      location: us-central1
      backendId: full
      appId: 1:0000000000:web:674cde32020e16fbce9dbd
      servingLocality: GLOBAL_ACCESS
      serviceAccount: ${serviceAccount.email}
    options:
      dependsOn:
        - ${fah}
  ### Include these blocks only once per project if you are starting from scratch ###
  serviceAccount:
    type: gcp:serviceaccount:Account
    name: service_account
    properties:
      project: my-project-name
      accountId: firebase-app-hosting-compute
      displayName: Firebase App Hosting compute service account
      createIgnoreAlreadyExists: true
  appHostingSaRunner:
    type: gcp:projects:IAMMember
    name: app_hosting_sa_runner
    properties:
      project: my-project-name
      role: roles/firebaseapphosting.computeRunner
      member: ${serviceAccount.member}
  fah:
    type: gcp:projects:Service
    properties:
      project: my-project-name
      service: firebaseapphosting.googleapis.com

The displayName property provides a human-readable identifier. The annotations property stores arbitrary metadata that external tools can read but not query. The labels property enables filtering and cost allocation across builds. Both are key-value maps that help you track builds across environments and teams.

Build from a GitHub repository branch

Continuous deployment workflows trigger builds directly from source control, eliminating manual container image management.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const my_repository = new gcp.developerconnect.GitRepositoryLink("my-repository", {
    project: "my-project-name",
    location: "us-central1",
    service: "developerconnect.googleapis.com",
});
const exampleAppHostingBackend = new gcp.firebase.AppHostingBackend("example", {
    project: "my-project-name",
    location: "us-central1",
    backendId: "mini",
    appId: "1:0000000000:web:674cde32020e16fbce9dbd",
    displayName: "My Backend",
    servingLocality: "GLOBAL_ACCESS",
    serviceAccount: "firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
    environment: "prod",
    annotations: {
        key: "value",
    },
    labels: {
        key: "value",
    },
    codebase: {
        repository: my_repository.name,
        rootDirectory: "/",
    },
});
const example = new gcp.firebase.AppHostingBuild("example", {
    project: exampleAppHostingBackend.project,
    location: exampleAppHostingBackend.location,
    backend: exampleAppHostingBackend.backendId,
    buildId: "gh-build",
    source: {
        codebase: {
            branch: "main",
        },
    },
});
const devconnect_secret = new gcp.projects.IAMMember("devconnect-secret", {
    project: "my-project-name",
    role: "roles/secretmanager.admin",
    member: devconnect_p4sa.member,
});
//##
//## Include these blocks only once per Github account ###
const my_connection = new gcp.developerconnect.Connection("my-connection", {
    project: "my-project-name",
    location: "us-central1",
    connectionId: "tf-test-connection-new",
    githubConfig: {
        githubApp: "FIREBASE",
    },
}, {
    dependsOn: [devconnect_secret],
});
export const nextSteps = my_connection.installationStates;
import pulumi
import pulumi_gcp as gcp

my_repository = gcp.developerconnect.GitRepositoryLink("my-repository",
    project="my-project-name",
    location="us-central1",
    service="developerconnect.googleapis.com")
example_app_hosting_backend = gcp.firebase.AppHostingBackend("example",
    project="my-project-name",
    location="us-central1",
    backend_id="mini",
    app_id="1:0000000000:web:674cde32020e16fbce9dbd",
    display_name="My Backend",
    serving_locality="GLOBAL_ACCESS",
    service_account="firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
    environment="prod",
    annotations={
        "key": "value",
    },
    labels={
        "key": "value",
    },
    codebase={
        "repository": my_repository.name,
        "root_directory": "/",
    })
example = gcp.firebase.AppHostingBuild("example",
    project=example_app_hosting_backend.project,
    location=example_app_hosting_backend.location,
    backend=example_app_hosting_backend.backend_id,
    build_id="gh-build",
    source={
        "codebase": {
            "branch": "main",
        },
    })
devconnect_secret = gcp.projects.IAMMember("devconnect-secret",
    project="my-project-name",
    role="roles/secretmanager.admin",
    member=devconnect_p4sa["member"])
###
### Include these blocks only once per Github account ###
my_connection = gcp.developerconnect.Connection("my-connection",
    project="my-project-name",
    location="us-central1",
    connection_id="tf-test-connection-new",
    github_config={
        "github_app": "FIREBASE",
    },
    opts = pulumi.ResourceOptions(depends_on=[devconnect_secret]))
pulumi.export("nextSteps", my_connection.installation_states)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/developerconnect"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firebase"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		my_repository, err := developerconnect.NewGitRepositoryLink(ctx, "my-repository", &developerconnect.GitRepositoryLinkArgs{
			Project:  pulumi.String("my-project-name"),
			Location: pulumi.String("us-central1"),
			Service:  "developerconnect.googleapis.com",
		})
		if err != nil {
			return err
		}
		exampleAppHostingBackend, err := firebase.NewAppHostingBackend(ctx, "example", &firebase.AppHostingBackendArgs{
			Project:         pulumi.String("my-project-name"),
			Location:        pulumi.String("us-central1"),
			BackendId:       pulumi.String("mini"),
			AppId:           pulumi.String("1:0000000000:web:674cde32020e16fbce9dbd"),
			DisplayName:     pulumi.String("My Backend"),
			ServingLocality: pulumi.String("GLOBAL_ACCESS"),
			ServiceAccount:  pulumi.String("firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com"),
			Environment:     pulumi.String("prod"),
			Annotations: pulumi.StringMap{
				"key": pulumi.String("value"),
			},
			Labels: pulumi.StringMap{
				"key": pulumi.String("value"),
			},
			Codebase: &firebase.AppHostingBackendCodebaseArgs{
				Repository:    my_repository.Name,
				RootDirectory: pulumi.String("/"),
			},
		})
		if err != nil {
			return err
		}
		_, err = firebase.NewAppHostingBuild(ctx, "example", &firebase.AppHostingBuildArgs{
			Project:  exampleAppHostingBackend.Project,
			Location: exampleAppHostingBackend.Location,
			Backend:  exampleAppHostingBackend.BackendId,
			BuildId:  pulumi.String("gh-build"),
			Source: &firebase.AppHostingBuildSourceArgs{
				Codebase: &firebase.AppHostingBuildSourceCodebaseArgs{
					Branch: pulumi.String("main"),
				},
			},
		})
		if err != nil {
			return err
		}
		devconnect_secret, err := projects.NewIAMMember(ctx, "devconnect-secret", &projects.IAMMemberArgs{
			Project: pulumi.String("my-project-name"),
			Role:    pulumi.String("roles/secretmanager.admin"),
			Member:  pulumi.Any(devconnect_p4sa.Member),
		})
		if err != nil {
			return err
		}
		// ## Include these blocks only once per Github account ###
		my_connection, err := developerconnect.NewConnection(ctx, "my-connection", &developerconnect.ConnectionArgs{
			Project:      pulumi.String("my-project-name"),
			Location:     pulumi.String("us-central1"),
			ConnectionId: pulumi.String("tf-test-connection-new"),
			GithubConfig: &developerconnect.ConnectionGithubConfigArgs{
				GithubApp: pulumi.String("FIREBASE"),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			devconnect_secret,
		}))
		if err != nil {
			return err
		}
		ctx.Export("nextSteps", my_connection.InstallationStates)
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var my_repository = new Gcp.DeveloperConnect.GitRepositoryLink("my-repository", new()
    {
        Project = "my-project-name",
        Location = "us-central1",
        Service = "developerconnect.googleapis.com",
    });

    var exampleAppHostingBackend = new Gcp.Firebase.AppHostingBackend("example", new()
    {
        Project = "my-project-name",
        Location = "us-central1",
        BackendId = "mini",
        AppId = "1:0000000000:web:674cde32020e16fbce9dbd",
        DisplayName = "My Backend",
        ServingLocality = "GLOBAL_ACCESS",
        ServiceAccount = "firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com",
        Environment = "prod",
        Annotations = 
        {
            { "key", "value" },
        },
        Labels = 
        {
            { "key", "value" },
        },
        Codebase = new Gcp.Firebase.Inputs.AppHostingBackendCodebaseArgs
        {
            Repository = my_repository.Name,
            RootDirectory = "/",
        },
    });

    var example = new Gcp.Firebase.AppHostingBuild("example", new()
    {
        Project = exampleAppHostingBackend.Project,
        Location = exampleAppHostingBackend.Location,
        Backend = exampleAppHostingBackend.BackendId,
        BuildId = "gh-build",
        Source = new Gcp.Firebase.Inputs.AppHostingBuildSourceArgs
        {
            Codebase = new Gcp.Firebase.Inputs.AppHostingBuildSourceCodebaseArgs
            {
                Branch = "main",
            },
        },
    });

    var devconnect_secret = new Gcp.Projects.IAMMember("devconnect-secret", new()
    {
        Project = "my-project-name",
        Role = "roles/secretmanager.admin",
        Member = devconnect_p4sa.Member,
    });

    //##
    //## Include these blocks only once per Github account ###
    var my_connection = new Gcp.DeveloperConnect.Connection("my-connection", new()
    {
        Project = "my-project-name",
        Location = "us-central1",
        ConnectionId = "tf-test-connection-new",
        GithubConfig = new Gcp.DeveloperConnect.Inputs.ConnectionGithubConfigArgs
        {
            GithubApp = "FIREBASE",
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            devconnect_secret,
        },
    });

    return new Dictionary<string, object?>
    {
        ["nextSteps"] = my_connection.InstallationStates,
    };
});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.developerconnect.GitRepositoryLink;
import com.pulumi.gcp.developerconnect.GitRepositoryLinkArgs;
import com.pulumi.gcp.firebase.AppHostingBackend;
import com.pulumi.gcp.firebase.AppHostingBackendArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBackendCodebaseArgs;
import com.pulumi.gcp.firebase.AppHostingBuild;
import com.pulumi.gcp.firebase.AppHostingBuildArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceArgs;
import com.pulumi.gcp.firebase.inputs.AppHostingBuildSourceCodebaseArgs;
import com.pulumi.gcp.projects.IAMMember;
import com.pulumi.gcp.projects.IAMMemberArgs;
import com.pulumi.gcp.developerconnect.Connection;
import com.pulumi.gcp.developerconnect.ConnectionArgs;
import com.pulumi.gcp.developerconnect.inputs.ConnectionGithubConfigArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var my_repository = new GitRepositoryLink("my-repository", GitRepositoryLinkArgs.builder()
            .project("my-project-name")
            .location("us-central1")
            .service("developerconnect.googleapis.com")
            .build());

        var exampleAppHostingBackend = new AppHostingBackend("exampleAppHostingBackend", AppHostingBackendArgs.builder()
            .project("my-project-name")
            .location("us-central1")
            .backendId("mini")
            .appId("1:0000000000:web:674cde32020e16fbce9dbd")
            .displayName("My Backend")
            .servingLocality("GLOBAL_ACCESS")
            .serviceAccount("firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com")
            .environment("prod")
            .annotations(Map.of("key", "value"))
            .labels(Map.of("key", "value"))
            .codebase(AppHostingBackendCodebaseArgs.builder()
                .repository(my_repository.name())
                .rootDirectory("/")
                .build())
            .build());

        var example = new AppHostingBuild("example", AppHostingBuildArgs.builder()
            .project(exampleAppHostingBackend.project())
            .location(exampleAppHostingBackend.location())
            .backend(exampleAppHostingBackend.backendId())
            .buildId("gh-build")
            .source(AppHostingBuildSourceArgs.builder()
                .codebase(AppHostingBuildSourceCodebaseArgs.builder()
                    .branch("main")
                    .build())
                .build())
            .build());

        var devconnect_secret = new IAMMember("devconnect-secret", IAMMemberArgs.builder()
            .project("my-project-name")
            .role("roles/secretmanager.admin")
            .member(devconnect_p4sa.member())
            .build());

        //##
        //## Include these blocks only once per Github account ###
        var my_connection = new Connection("my-connection", ConnectionArgs.builder()
            .project("my-project-name")
            .location("us-central1")
            .connectionId("tf-test-connection-new")
            .githubConfig(ConnectionGithubConfigArgs.builder()
                .githubApp("FIREBASE")
                .build())
            .build(), CustomResourceOptions.builder()
                .dependsOn(devconnect_secret)
                .build());

        ctx.export("nextSteps", my_connection.installationStates());
    }
}
resources:
  example:
    type: gcp:firebase:AppHostingBuild
    properties:
      project: ${exampleAppHostingBackend.project}
      location: ${exampleAppHostingBackend.location}
      backend: ${exampleAppHostingBackend.backendId}
      buildId: gh-build
      source:
        codebase:
          branch: main
  exampleAppHostingBackend:
    type: gcp:firebase:AppHostingBackend
    name: example
    properties:
      project: my-project-name
      location: us-central1
      backendId: mini
      appId: 1:0000000000:web:674cde32020e16fbce9dbd
      displayName: My Backend
      servingLocality: GLOBAL_ACCESS
      serviceAccount: firebase-app-hosting-compute@my-project-name.iam.gserviceaccount.com
      environment: prod
      annotations:
        key: value
      labels:
        key: value
      codebase:
        repository: ${["my-repository"].name}
        rootDirectory: /
  my-repository:
    type: gcp:developerconnect:GitRepositoryLink
    properties:
      project: my-project-name
      location: us-central1
      service: developerconnect.googleapis.com
  devconnect-secret: ###
    type: gcp:projects:IAMMember
    properties:
      project: my-project-name
      role: roles/secretmanager.admin
      member: ${["devconnect-p4sa"].member}
  ### Include these blocks only once per Github account ###
  my-connection:
    type: gcp:developerconnect:Connection
    properties:
      project: my-project-name
      location: us-central1
      connectionId: tf-test-connection-new
      githubConfig:
        githubApp: FIREBASE
    options:
      dependsOn:
        - ${["devconnect-secret"]}
outputs:
  nextSteps: ${["my-connection"].installationStates}

When using source.codebase, the branch property specifies which Git branch to build from. The backend must reference a GitRepositoryLink resource that connects to your GitHub repository via Developer Connect. This configuration triggers Cloud Build to clone the repository, build the container image, and deploy it to Cloud Run automatically. The service account needs secretmanager.admin permissions for Developer Connect to access GitHub credentials.

Beyond these examples

These snippets focus on specific build-level features: container image and source code deployment, metadata tagging with annotations and labels, and GitHub integration via Developer Connect. They’re intentionally minimal rather than full deployment pipelines.

The examples rely on pre-existing infrastructure such as Firebase App Hosting backend, service account with firebaseapphosting.computeRunner role, container images in Artifact Registry (for container deployments), and Developer Connect GitHub connection (for source builds). They focus on configuring the build rather than provisioning the surrounding infrastructure.

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

  • Build state monitoring and error handling
  • Cloud Build logs access (buildLogsUri)
  • Build environment configuration
  • Multi-environment deployment strategies

These omissions are intentional: the goal is to illustrate how each build feature is wired, not provide drop-in CI/CD modules. See the Firebase App Hosting Build resource reference for all available configuration options.

Let's deploy GCP Firebase App Hosting Builds

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Build Sources & Deployment
What are the two ways to specify a build source?
You can deploy from either a container image (using source.container.image) or a Github repository (using source.codebase.branch). The source type is immutable after creation.
How do I deploy from a container image?
Set source.container.image to your container image URI, such as us-docker.pkg.dev/cloudrun/container/hello.
How do I deploy from a Github repository?
Configure source.codebase.branch with your branch name (e.g., main), create a Developer Connect GitRepositoryLink, and set the backend’s codebase.repository property. You’ll also need a Developer Connect Connection with githubConfig.githubApp set to FIREBASE.
IAM & Permissions
What IAM role does my service account need?
The service account must have the roles/firebaseapphosting.computeRunner role to run App Hosting builds.
What additional permissions are needed for Github integration?
Grant the Developer Connect service account the roles/secretmanager.admin role to access Github credentials.
Immutability & Updates
What properties can't be changed after creating a build?
The following properties are immutable: backend, buildId, location, project, source, annotations, and displayName. Changing any of these requires recreating the resource.
Why don't my annotation or label changes show up?
The annotations and labels fields are non-authoritative, meaning they only manage values in your configuration. Use effectiveAnnotations and effectiveLabels to see all values, including those set by other tools.
Configuration & Metadata
What's the character limit for displayName?
The displayName field has a 63-character limit.
What's the difference between annotations and labels?
Both are key-value pairs, but annotations store arbitrary metadata (not queryable), while labels are used to organize and categorize resources. Both are non-authoritative fields.
Troubleshooting & Build States
What do the different build states mean?
Builds progress through states: BUILDING (in progress), BUILT (image created), DEPLOYING (deploying to Cloud Run), READY (successfully deployed), or FAILED (error occurred).
How do I troubleshoot a failed build?
Check the state property for FAILED, then examine errorSource (either CLOUD_BUILD or CLOUD_RUN) and errors for details. Access full logs via the buildLogsUri property.

Using a different cloud?

Explore compute guides for other cloud providers: