Set up and manage Firebase projects and products via Terraform

1. Introduction

Goals

You can use Terraform to set up and manage a Firebase project, including the programmatic configuration of infrastructure and Firebase products.

This codelab first describes how to build a Terraform configuration file to create a new Firebase project, followed by how to configure the apps and Firebase products that you want to use in that project. We also cover the basics of the Terraform command line, like previewing changes to be made and then implementing them.

If you've wanted to learn how to set up and manage Firebase projects and products with Terraform, then this codelab is for you!

What you'll learn

  • How to create a Terraform config file (*.tf)
  • How to use Terraform CLI commands to manage your infrastructure
  • How to modify your configuration to update your resources and services
  • How to apply your configuration on a real web app (called Friendly Chat)
  • How to define parallel (and in-sync) configurations in different environments (production, staging, etc)

What you'll need

To be successful with this codelab, you need basic proficiency with Terraform and its terminology, including the following prerequisites:

  • Install Terraform and familiarize yourself with Terraform using their official tutorials

This codelab provides a real sample app so that you can test and interact with what you provision via Terraform. To do this, you'll need the following:

  • The sample code for a web app - download this code in the next step of the codelab
  • The package manager npm (which typically comes with Node.js) - install these tools
  • The Firebase CLI - install this CLI and log in

2. Get the starting code

In this codelab, you can test out what you provision via Terraform with a real web app. We recommend doing this so that you understand all the steps needed to use Terraform-provisioned resources.

Clone the codelab's GitHub repository from the command line:

git clone https://github.com/firebase/codelab-friendlychat-web

Alternatively, if you do not have git installed, you can download the repository as a ZIP file.

3. Create a Terraform configuration

Terraform set up

  1. In the codebase of the downloaded sample app, navigate to the root of the web directory.
  2. At the root of that directory, create a Terraform config file called main.tf with the following initial setup:

    main.tf
    # Terraform configuration to set up providers by version.
    terraform {
      required_providers {
        google-beta = {
          source  = "hashicorp/google-beta"
          version = "~> 4.0"
        }
      }
    }
    
    # Configure the provider not to use the specified project for quota check.
    # This provider should only be used during project creation and initializing services.
    provider "google-beta" {
      alias                 = "no_user_project_override"
      user_project_override = false
    }
    
    # Configure the provider that uses the new project's quota.
    provider "google-beta" {
      user_project_override = true
    }
    

Each of the google-beta providers has an attribute named user_project_override that determines how the operations from Terraform will be quota checked. For provisioning most resources, you should use user_project_override = true, which means to check quota against your own Firebase project. However, to set up your new project so that it can accept quota checks, you first need to use user_project_override=false. The Terraform alias syntax allows you to distinguish between the two provider setups in the next steps of this codelab.

Initialize Terraform in the directory

Creating a new config for the first time requires downloading the provider specified in the configuration.

To do this initialization, run the following command from the root of the same directory as your main.tf config file:

terraform init

4. Create a Firebase project via Terraform

To "create a Firebase project", it's important to remember that each Firebase project is actually a Google Cloud project, just with Firebase services enabled for it.

Add blocks for the underlying Google Cloud project and APIs

  1. First, provision the underlying Google Cloud project.

    To your main.tf config file, add the following resource block.

    You need to specify your own project name (like "Terraform FriendlyChat Codelab") and your own project ID (like "terraform-codelab-your-initials"). Note that the name value is only used within Firebase interfaces and isn't visible to end-users. The project_id value, though, uniquely identifies your project to Google, so make sure you specify a unique value. main.tf
    ...
    
    # Create a new Google Cloud project.
    resource "google_project" "default" {
      provider = google-beta.no_user_project_override
    
      name            = "<PROJECT_NAME_OF_YOUR_PROJECT>"
      project_id      = "<PROJECT_ID_OF_YOUR_PROJECT>"
    
      # Required for the project to display in any list of Firebase projects.
      labels = {
        "firebase" = "enabled"
      }
    }
    
  2. Next, you need to enable the required underlying APIs: the Service Usage API and Firebase Management API.

    This API enablement is usually handled behind the scenes when you use the Firebase console to create a Firebase project, but Terraform needs to be explicitly told to do this enablement.

    To your main.tf config file (just under the block that creates the new Cloud project), add the following resource block:

    main.tf
    ...
    
    # Enable the required underlying Service Usage API.
    resource "google_project_service" "serviceusage" {
      provider = google-beta.no_user_project_override
    
      project = google_project.default.project_id
      service = "serviceusage.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enable the required underlying Firebase Management API.
    resource "google_project_service" "firebase" {
      provider = google-beta.no_user_project_override
    
      project = google_project.default.project_id
      service = "firebase.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    By enabling the Service Usage API, your new project will be able to accept quota checks! So, for all subsequent resource provisioning and service enabling, you should use the provider with user_project_override (no alias needed).

Add a block to enable Firebase services

The very last thing that's required to "create a Firebase project" is enabling Firebase services on the project.

Continuing in your main.tf config file, add the following resource block.

As mentioned just above, note that this resource block is using the provider with user_project_override (no alias needed).

main.tf

...

# Enable Firebase services for the new project created above.
resource "google_firebase_project" "default" {
  provider = google-beta

  project = google_project.default.project_id

  # Wait until the required APIs are enabled.
  depends_on = [
    google_project_service.firebase,
    google_project_service.serviceusage,
  ]
}

In the resource block above, you might notice the depends_on clause, which tells Terraform to wait for the underlying APIs to be enabled. Without this clause, Terraform doesn't know about the dependency and may run into errors when provisioning resources in parallel.

Apply the configuration

  1. To provision the new resources and enable the APIs specified in your config file, run the following command from the root of the same directory as your main.tf file (which should be web):
    terraform apply
    
  2. In the terminal, Terraform prints a plan of actions it will perform.

    If everything looks as expected, approve the actions by entering yes.

    main.tf
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
      # google_firebase_project.default will be created
      + resource "google_firebase_project" "default" {
          + display_name   = (known after apply)
          + id             = (known after apply)
          + project        = "terraform-friendlychat-codelab"
          + project_number = (known after apply)
        }
    
      # google_project.default will be created
      + resource "google_project" "default" {
          + auto_create_network = true
          + id                  = (known after apply)
          + labels              = {
              + "firebase" = "enabled"
            }
          + name                = "Terraform FriendlyChat Codelab"
          + number              = (known after apply)
          + project_id          = "terraform-friendlychat-codelab"
          + skip_delete         = (known after apply)
        }
    
      # google_project_service.firebase will be created
      + resource "google_project_service" "firebase" {
          + disable_on_destroy = false
          + id                 = (known after apply)
          + project            = "terraform-friendlychat-codelab"
          + service            = "firebase.googleapis.com"
        }
    
      # google_project_service.serviceusage will be created
      + resource "google_project_service" "serviceusage" {
          + disable_on_destroy = false
          + id                 = (known after apply)
          + project            = "terraform-friendlychat-codelab"
          + service            = "serviceusage.googleapis.com"
        }
    
    Plan: 4 to add, 0 to change, 0 to destroy.
    
    Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.
    
      Enter a value: yes # <----
    

Note that if you only need to preview the changes without applying, you can use the terraform plan command instead.

Validate the changes

After Terraform finishes running, you can inspect the state of all Terraform provisioned resources and services enabled by running the following command:

terraform show

Here's an example of what you should see printed. Your state will contain values specific to your project.

# google_firebase_project.default:
resource "google_firebase_project" "default" {
    display_name   = "Terraform FriendlyChat Codelab"
    id             = "projects/terraform-friendlychat-codelab"
    project        = "terraform-friendlychat-codelab"
    project_number = "000000000"
}

# google_project.default:
resource "google_project" "default" {
    auto_create_network = true
    id                  = "projects/terraform-friendlychat-codelab"
    labels              = {
        "firebase" = "enabled"
    }
    name                = "Terraform FriendlyChat Codelab"
    number              = "000000000"
    project_id          = "terraform-friendlychat-codelab"
}

# google_project_service.firebase:
resource "google_project_service" "firebase" {
    disable_on_destroy = false
    id                 = "terraform-friendlychat-codelab/firebase.googleapis.com"
    project            = "terraform-friendlychat-codelab"
    service            = "firebase.googleapis.com"
}

# google_project_service.serviceusage:
resource "google_project_service" "serviceusage" {
    disable_on_destroy = false
    id                 = "terraform-friendlychat-codelab/serviceusage.googleapis.com"
    project            = "terraform-friendlychat-codelab"
    service            = "serviceusage.googleapis.com"
}

Alternatively, you can verify that the project is created by viewing it in the Firebase console.

The Terraform FriendlyChat Codelab project selected on the Firebase console

5. Register your Firebase app via Terraform

To use Firebase, you need to register each platform-variant of your app in your Firebase project. In this codelab, you'll use a real app to test and interact with what you provision via Terraform. This app is a web app, so you need to tell Terraform to register a Firebase Web App in your newly created Firebase project.

Add a block to register the web app

To register your web app in your Firebase project, append your main.tf file with the following resource block.

You need to specify your own display_name for your web app. Note that this name is only used within Firebase interfaces and isn't visible to end-users.

main.tf

...

# Create a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "default" {
  provider = google-beta

  project      = google_firebase_project.default.project
  display_name = "<DISPLAY_NAME_OF_YOUR_WEB_APP>"
  deletion_policy = "DELETE"
}

Apply the configuration

  1. To provision the new resource, run the following command from the root of the same directory as your main.tf file (which should be web).
    terraform apply
    
    Note that this command won't re-create a new Google Cloud project. Terraform will detect that a project with the specified project ID already exists and will compare the current state of the project with what's in the .tf file and make any changes that it finds.
  2. Review the printed plan of actions. If everything looks as expected, type yes and press Enter to approve the actions.

Validate the changes

You can inspect the state of the newly provisioned resource by running the following command:

terraform show

Alternatively, you can verify that the app was successfully registered in your project by viewing it in the Firebase console. Go to Project settings, and then scroll down to the Your apps section.

6. Set up Firebase Authentication

Authentication is an important piece of any app. To allow end-users to sign in to your web app with their Google accounts, you can enable Firebase Authentication and set up the sign-in with Google method.

Note that in this codelab, we provide two different options to set up Firebase Authentication:

  • Option 1 (Recommended): Set up Firebase Authentication in the console, which doesn't require GCIP.
    • Using this option means that you do not have to associate your new project with a Cloud Billing account.
  • Option 2: Set up Firebase Authentication via Terraform using Google Cloud Identity Platform (GCIP) APIs.
    • Using this option means that you have to associate your new project with a Cloud Billing account since GCIP requires the project to be on the Blaze pricing plan.

Option 1: Set up Authentication using the Firebase console

To set up Firebase Authentication using the Firebase console, your project doesn't need to be on the Blaze pricing plan.

Here's how to set up Firebase Authentication and sign-in with Google:

  1. In the Firebase console, locate the Build section in the left panel.
  2. Click Authentication, click Get started, and then click the Sign-in method tab (or click here to go directly there).
  3. Click Add new provider and, from the Additional providers section, select Google.
  4. Activate the Enable toggle.
  5. Set the public-facing name of your app to something like FriendlyChat (this doesn't need to be globally unique).
  6. Choose a Project support email from the dropdown menu, and then click Save.Configuring Firebase Auth on the Firebase console
  7. You should see Google as an enabled sign-in provider.Firebase console Authentication page: Google sign-in enabled

Option 2: Set up Authentication via Terraform using Google Cloud Identity Platform (GCIP) APIs

To set up Firebase Authentication via Terraform, you must use GCIP APIs, which means the project must be on the Blaze pricing plan. You upgrade your Firebase project to use the Blaze plan by associating a Cloud Billing account with the project.

Enable billing via Terraform

  1. If you don't already have a Cloud Billing account, the first step is to create a new account in the Google Cloud Console. When you do this, note its Billing account ID. The Billing account ID can be located on the Billing page in the Billing account ID associated with your project.Enabling a billing account using the Google Cloud console
  2. To enable billing in your project via Terraform, add a billing_account attribute into the existing google_project resource in your main.tf file:

    main.tf
    ...
    
    # Create a new Google Cloud project.
    resource "google_project" "default" {
      provider = google-beta.no_user_project_override
    
      name            = "<PROJECT_NAME_OF_YOUR_PROJECT>"
      project_id      = "<PROJECT_ID_OF_YOUR_PROJECT>"
      billing_account = "<YOUR_BILLING_ACCOUNT_ID>" # Add this line with your Cloud Billing account ID
    
      # Required for the project to display in any list of Firebase projects.
      labels = {
        "firebase" = "enabled"
      }
    }
    
    ...
    

Enable Firebase Authentication and sign-in with Google via Terraform

  1. To provision Firebase Authentication with GCIP, append your main.tf file with the following resource blocks:

    main.tf
    ...
    
    # Enable the Identity Toolkit API.
    resource "google_project_service" "auth" {
      provider = google-beta
    
      project  = google_firebase_project.default.project
      service =  "identitytoolkit.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Create an Identity Platform config.
    # Also, enable Firebase Authentication using Identity Platform (if Authentication isn't yet enabled).
    resource "google_identity_platform_config" "auth" {
      provider = google-beta
      project  = google_firebase_project.default.project
    
      # For example, you can configure to auto-delete anonymous users.
      autodelete_anonymous_users = true
    
      # Wait for identitytoolkit.googleapis.com to be enabled before initializing Authentication.
      depends_on = [
        google_project_service.auth,
      ]
    }
    
  2. Enabling sign-in with Google requires that you have an OAuth client. Go to the APIs & Services section of the Google Cloud Console to do this setup.
  3. Since this is your first time creating a client ID for this project, you need to configure your OAuth consent screen.
    1. Open the OAuth consent screen page, and then select the project you just created.
    2. Set the User Type to External, and then click Create.
    3. In the next screen, complete the following, and then click Save and continue.
      • Set the public-facing App name of your app to something like FriendlyChat (this doesn't need to be globally unique).
      • Choose a User support email from the dropdown menu.
      • Enter an email for the Developer contact information.
    4. In the next screens, complete the following:
      • Accept the defaults on the Scopes page, and then click Save and Continue.
      • Accept the defaults on the Test users page, and then click Save and Continue.
      • Review the summary, and then click Back to dashboard.
      Configuring an OAuth2 client using the Google Cloud console
  4. Set up an OAuth client in the Credentials page by doing the following:
    1. Click Create credentials and select OAuth client ID.
    2. From the Application type drop-down, select Web application.
    3. In the Name field, enter the name of your app, for example FriendlyChat (this doesn't need to be globally unique).
    4. Allow your app's URL to use this OAuth client, by setting the following:
      • Under Authorized JavaScript origins, click Add URI and enter
        https://<PROJECT_ID>.firebaseapp.com, where <PROJECT_ID> is the project ID you set in main.tf.
      • Under Authorized redirect URIs, click Add URI and enter
        https://<PROJECT_ID>.firebaseapp.com/__/auth/handler, where <PROJECT_ID> is the project ID you set in main.tf.
    5. Click Save.
    Obtaining the OAuth2 Client ID and secret from the Google Cloud console Credentials page
  5. To enable sign-in with Google using your OAuth client ID and client secret, append your main.tf file with the following block:

    main.tf
    ...
    
    variable "oauth_client_secret" {
      type = string
    
      description = "OAuth client secret. For this codelab, you can pass in this secret through the environment variable TF_VAR_oauth_client_secret. In a real app, you should use a secret manager service."
    
      sensitive = true
    }
    
    resource "google_identity_platform_default_supported_idp_config" "google_sign_in" {
      provider = google-beta
      project  = google_firebase_project.default.project
    
      enabled       = true
      idp_id        = "google.com"
      client_id     = "<YOUR_OAUTH_CLIENT_ID>"
      client_secret = var.oauth_client_secret
    
      depends_on = [
         google_identity_platform_config.auth
      ]
    }
    

Apply the configuration

  1. To set up Authentication according to your config, run the following commands from the root of the same directory as your main.tf file (which should be web):
    export TF_VAR_oauth_client_secret="<YOUR_OAUTH_CLIENT_SECRET>"
    
    terraform apply
    
    Note that running terraform apply won't re-create a new Google Cloud project. Terraform will detect that a project with the specified project ID already exists and will compare the current state of the project with what's in the .tf file. It will then make any changes that it finds.
  2. Review the printed plan of actions. If everything looks as expected, type yes and press Enter to approve the actions.

Validate the changes

  1. In the Firebase console, locate the Build section in the left panel.
  2. Click Authentication, and then click the Sign-in method tab (or click here to go directly there).
  3. You should see Google as an enabled sign-in provider.Firebase console Authentication page: Google sign-in enabled

7. Set up a Firestore database and its Security Rules

For this codelab's web app, you'll store messages between end-users in a Firestore database.

  1. To enable the required APIs and provision the database instance, append your main.tf file with the following resource blocks:

    main.tf
    ...
    
    # Enable required APIs for Cloud Firestore.
    resource "google_project_service" "firestore" {
      provider = google-beta
    
      project  = google_firebase_project.default.project
      for_each = toset([
        "firestore.googleapis.com",
        "firebaserules.googleapis.com",
      ])
      service = each.key
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Provision the Firestore database instance.
    resource "google_firestore_database" "default" {
      provider                    = google-beta
    
      project                     = google_firebase_project.default.project
      name                        = "(default)"
      # See available locations:
      # https://firebase.google.com/docs/firestore/locations
      location_id                 = "<NAME_OF_DESIRED_REGION>"
      # "FIRESTORE_NATIVE" is required to use Firestore with Firebase SDKs,
      # authentication, and Firebase Security Rules.
      type                        = "FIRESTORE_NATIVE"
      concurrency_mode            = "OPTIMISTIC"
    
      depends_on = [
        google_project_service.firestore
      ]
    }
    
  2. Change <NAME_OF_DESIRED_REGION> to the region where you want the database to reside.

    When developing a production app, you'll want this to be in a region close to the majority of the users and in-common with other Firebase services, like Cloud Functions. For this codelab, you can use us-east1 (South Carolina) or use the region closest to you (see Cloud Firestore locations).
  3. Every Firestore database instance that's accessible to Firebase must be protected by Firebase Security Rules.

    This codelab's sample code provides a set of secure Firestore rules in the file firestore.rules, which you can find at the root of the web directory.
  4. Append your main.tf file with the following resource blocks to do the following:
    • Create a ruleset of Firebase Security Rules from the local firestore.rules file.
    • Release the ruleset for the Firestore instance.
    Note that these resource blocks accomplish the equivalent of clicking the Publish button in the Firebase console or running firebase deploy --only firestore:rules.

    main.tf
    ...
    
    # Create a ruleset of Firestore Security Rules from a local file.
    resource "google_firebaserules_ruleset" "firestore" {
      provider = google-beta
    
      project  = google_firebase_project.default.project
      source {
        files {
          name = "firestore.rules"
          # Write security rules in a local file named "firestore.rules".
          # Learn more: https://firebase.google.com/docs/firestore/security/get-started
          content = file("firestore.rules")
        }
      }
    
      # Wait for Firestore to be provisioned before creating this ruleset.
      depends_on = [
        google_firestore_database.default,
      ]
    }
    
    # Release the ruleset for the Firestore instance.
    resource "google_firebaserules_release" "firestore" {
      provider     = google-beta
    
      name         = "cloud.firestore"  # must be cloud.firestore
      ruleset_name = google_firebaserules_ruleset.firestore.name
      project      = google_firebase_project.default.project
    
      # Wait for Firestore to be provisioned before releasing the ruleset.
      depends_on = [
        google_firestore_database.default,
      ]
    
      lifecycle {
        replace_triggered_by = [
          google_firebaserules_ruleset.firestore
        ]
      }
    }
    
  5. Run terraform apply to provision the Firestore database and deploy its security rules.
  6. Verify that the database is provisioned and that its security rules are deployed:
    1. In the Firebase console, locate the Build section in the left panel.
    2. Go to the Firestore Database section, and then click the Rules tab.
    Verifying Cloud Firestore rules using the Firebase console

8. Set up a Cloud Storage bucket and its Security Rules

For this codelab's web app, you'll store images shared between end-users in a Cloud Storage bucket.

  1. To enable the required APIs and provision your Cloud Storage default bucket, append your main.tf file with the following resource blocks.

    Note that the default Cloud Storage bucket for your project is provisioned via Google App Engine and must have the same location as your Firestore database. See App Engine locations for more information.

    If you want multiple buckets in your project, provision them using the google_storage_bucket resource (not shown in this codelab).

    main.tf
    ...
    
    # Enable required APIs for Cloud Storage for Firebase.
    resource "google_project_service" "storage" {
      provider = google-beta
    
      project  = google_firebase_project.default.project
      for_each = toset([
        "firebasestorage.googleapis.com",
        "storage.googleapis.com",
      ])
      service = each.key
    
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Provision the default Cloud Storage bucket for the project via Google App Engine.
    resource "google_app_engine_application" "default" {
      provider    = google-beta
    
      project     = google_firebase_project.default.project
      # See available locations: https://firebase.google.com/docs/projects/locations#default-cloud-location
      # This will set the location for the default Storage bucket and the App Engine App.
      location_id = "<NAME_OF_DESIRED_REGION_FOR_DEFAULT_BUCKET>"  # Must be in the same location as Firestore (above)
    
      # Wait until Firestore is provisioned first.
      depends_on = [
        google_firestore_database.default
      ]
    }
    
    # Make the default Storage bucket accessible for Firebase SDKs, authentication, and Firebase Security Rules.
    resource "google_firebase_storage_bucket" "default-bucket" {
      provider  = google-beta
    
      project   = google_firebase_project.default.project
      bucket_id = google_app_engine_application.default.default_bucket
    }
    
  2. Every Cloud Storage bucket that's accessible to Firebase must be protected by Firebase Security Rules.

    This codelab's sample code provides a set of secure Firestore rules in the file storage.rules, which you can find at the root of the web directory.
  3. Append your main.tf file with the following resource blocks to do the following:
    • Create a ruleset of Firebase Security Rules from the local file.
    • Release the ruleset for the Storage bucket.
    Note that these resource blocks accomplish the equivalent of clicking the Publish button in the Firebase console or running firebase deploy --only storage.

    main.tf
    ...
    
    # Create a ruleset of Cloud Storage Security Rules from a local file.
    resource "google_firebaserules_ruleset" "storage" {
      provider = google-beta
    
      project  = google_firebase_project.default.project
      source {
        files {
          # Write security rules in a local file named "storage.rules".
          # Learn more: https://firebase.google.com/docs/storage/security/get-started
          name    = "storage.rules"
          content = file("storage.rules")
        }
      }
    
      # Wait for the default Storage bucket to be provisioned before creating this ruleset.
      depends_on = [
        google_firebase_storage_bucket.default-bucket,
      ]
    }
    
    # Release the ruleset to the default Storage bucket.
    resource "google_firebaserules_release" "default-bucket" {
      provider     = google-beta
    
      name         = "firebase.storage/${google_app_engine_application.default.default_bucket}"
      ruleset_name = "projects/${google_firebase_project.default.project}/rulesets/${google_firebaserules_ruleset.storage.name}"
      project      = google_firebase_project.default.project
    
      lifecycle {
        replace_triggered_by = [
          google_firebaserules_ruleset.storage
        ]
      }
    }
    
  4. Run terraform apply to provision the default Cloud Storage bucket and deploy its security rules.
  5. Verify that the bucket is provisioned and that its security rules are deployed:
    1. In the Firebase console, locate the Build section in the left panel.
    2. Go to the Storage section, and then click the Rules tab.
    Verifying security rules using the Firebase console

9. Run your app locally

You're now ready to run your web app for the first time! You'll use the Firebase Hosting emulator to serve your app locally.

  1. Open a new terminal window and, from the web directory, run the following Firebase CLI command to start the emulator:
    firebase emulators:start --project=<PROJECT_ID>
    
  2. In your browser, open your web app at the local URL returned by the CLI (usually http://localhost:5000).

You should see your FriendlyChat app's UI, which is not (yet!) functioning. The app isn't connected to Firebase yet, but by completing the next steps of this codelab, it will be!

Note that whenever you make changes to your web app (like you'll do in the following steps of this codelab), refresh your browser to update the local URL with those changes.

10. Install, configure, and initialize Firebase

To get an app working with Firebase, your app needs the Firebase SDK and the Firebase configuration for your Firebase project.

The sample code for this codelab is already a working app with all the dependencies and required functions for using various Firebase products in the app. You can look in web/package.json and web/src/index.js if you'd like to see what's already been done.

Even though the sample code is mostly complete, you still need to do a few things to get your app running, including: install the Firebase SDK, start your build, add the Firebase configuration to your app, and finally initialize Firebase.

Install the Firebase SDK and start your webpack build

You need to run a few commands to start your app's build.

  1. Open a new terminal window.
  2. Make sure you're at the root of the web directory.
  3. Run npm install to download the Firebase SDK.
  4. Run npm update to update any dependencies.
  5. Run npm run start to start up webpack.

For the rest of the codelab, webpack will now continually rebuild your source code.

Add your Firebase configuration to your app

You also need to add your Firebase configuration to your app so that the Firebase SDKs know which Firebase project you want them to use.

For this codelab, you have two different options to get your Firebase configuration:

  • Option 1: Obtain your Firebase config from the Firebase console.
  • Option 2: Obtain your Firebase config via Terraform.

Option 1: Obtain the config from the Firebase console and add it to your codebase

  1. In the Firebase console, go to your Project settings.
  2. Scroll down to the Your apps card, and then select your web app.
  3. Select Config from the Firebase SDK snippet pane, and then copy the config snippet.
  4. Paste your config into your app's web/src/firebase-config.js file, like so:

    firebase-config.js
    ...
    
    const config = {
      apiKey: "<API_KEY>",
      authDomain: "<PROJECT_ID>.firebaseapp.com",
      projectId: "<PROJECT_ID>",
      storageBucket: "<PROJECT_ID>.appspot.com",
      messagingSenderId: "<SENDER_ID>",
      appId: "<APP_ID>",
      measurementId: "<G-MEASUREMENT_ID>",
    };
    
    ...
    

Option 2: Obtain the config via Terraform and add it to your codebase

Alternatively, you can get your Firebase configuration via Terraform as an output value in the CLI.

  1. In your main.tf file, find your google_firebase_web_app resource block (the block that registered a web app with your project).
  2. Immediately after that block, add the following blocks:

    main.tf
    ...
    
    data "google_firebase_web_app_config" "default" {
      provider     = google-beta
      project      = google_firebase_project.default.project
      web_app_id   = google_firebase_web_app.default.app_id
    }
    
    output "friendlychat_web_app_config" {
      value = {
        projectId         = google_firebase_project.default.project
        appId             = google_firebase_web_app.default.app_id
        apiKey            = data.google_firebase_web_app_config.default.api_key
        authDomain        = data.google_firebase_web_app_config.default.auth_domain
        storageBucket     = lookup(data.google_firebase_web_app_config.default, "storage_bucket", "")
        messagingSenderId = lookup(data.google_firebase_web_app_config.default, "messaging_sender_id", "")
        measurementId     = lookup(data.google_firebase_web_app_config.default, "measurement_id", "")
      }
    }
    
    ...
    
  3. Since the data block and the output block aren't intended for modifying the infrastructure in any way, you only need to run the following commands.
    1. To load the Firebase config of your web app into the Terraform state of your directory, run this command:
      terraform refresh
      
    2. To print the Firebase config values, run this command:
      terraform output –json
      
      The following is an example output of a config. Your printed output will contain your project and app's values.
      {
        "friendlychat_web_app_config": {
          "sensitive": false,
          "type": [
            "object",
            {
              "apiKey": "string",
              "appId": "string",
              "authDomain": "string",
              "measurementId": "string",
              "messagingSenderId": "string",
              "projectId": "string",
              "storageBucket": "string"
            }
          ],
          "value": {
            "apiKey": "<API_KEY>",
            "appId": "<APP_ID>",
            "authDomain": "<PROJECT_ID>.firebaseapp.com",
            "measurementId": "<G-MEASUREMENT_ID>",
            "messagingSenderId": "<SENDER_ID>",
            "projectId": "<PROJECT_ID>",
            "storageBucket": "<PROJECT_ID>.appspot.com"
          }
        }
      }
      
  4. Copy the values from within the value map.
  5. Paste these values (your config) into your app's web/src/firebase-config.js file, like so:

    firebase-config.js
    ...
    
    const config = {
      apiKey: "<API_KEY>",
      appId: "<APP_ID>",
      authDomain: "<PROJECT_ID>.firebaseapp.com",
      measurementId: "<G-MEASUREMENT_ID>",
      messagingSenderId: "<SENDER_ID>",
      projectId: "<PROJECT_ID>",
      storageBucket: "<PROJECT_ID>.appspot.com",
    };
    
    ...
    

Initialize Firebase in your app

Finally, to initialize Firebase, append your app's web/src/index.js file with the following:

index.js

...

const firebaseAppConfig = getFirebaseConfig();
initializeApp(firebaseAppConfig);

Try out your app

Now that everything is configured for Firebase, you can try out your functional web app.

  1. Refresh the browser serving your app.
  2. You should now be able to sign in with Google and start posting messages into the chat. If you have image files, you can even upload them!

11. Replicate your configuration across environments

Terraform excels at managing multiple similarly configured infrastructure (for example, setting up a staging Firebase project that's similar to a prod project).

In this codelab, you'll create a second Firebase project to be a staging environment.

To replicate an existing configuration to create this staging project, you have two options:

  • Option 1: Make a copy of the Terraform configuration.
    This option offers the most flexibility for how much the replicated project can differ from the source project.
  • Option 2: Reuse configurations with for_each.
    This option offers more code-reuse if each project should not differ in significant ways and you want to propagate changes to all projects at once.

Option 1: Make a copy of the Terraform configuration

This option offers the most flexibility for how much the replicated project can differ from the source project, such as having apps with different display names and staged rollouts.

  1. At the root of your web directory, create a new Terraform config file called main_staging.tf.
  2. Copy all the resource blocks from your main.tf file (except for the terraform and provider blocks), and then paste them into your main_staging.tf file.
  3. You then need to modify each of your replicated resource blocks in main_staging.tf for them to work with your staging project:
    • Resource labels: Use a new name to avoid conflict. For example, rename resource "google_project" "default" to resource "google_project" "staging".
    • Resource references: Update each one. For example, update google_firebase_project.default.project to google_firebase_project.staging.project.
    You can find the complete configuration of a main_staging.tf file in this codelab's GitHub repository:

    web/terraform-checkpoints/replicate-config/main_staging-copypaste.tf

    If you want to use this config, make sure you do the following:
    1. Copy the config from main_staging-copypaste.tf, and then paste it into your main_staging.tf file.
    2. In your main_staging.tf file, do the following:
      • In the google_project resource block, update the name attribute, the project-id attribute, and (if you set up Authentication via Terraform) the billing_account attribute with your own values.
      • In the google_firebase_web_app resource block, update the display_name attribute with your own value.
      • In the google_firestore_database and google_app_engine_application resource blocks, update the location_id attributes with your own value.
    main_staging.tf
    # Create a new Google Cloud project.
    resource "google_project" "staging" {
      provider = google-beta.no_user_project_override
    
      name            = "<PROJECT_NAME_OF_STAGING_PROJECT>"
      project_id      = "<PROJECT_ID_OF_STAGING_PROJECT"
      # Required if you want to set up Authentication via Terraform
      billing_account = "<YOUR_BILLING_ACCOUNT_ID>"
    
      # Required for the project to display in any list of Firebase projects.
      labels = {
        "firebase" = "enabled"
      }
    }
    
    # Enable the required underlying Service Usage API.
    resource "google_project_service" "staging_serviceusage" {
      provider = google-beta.no_user_project_override
    
      project = google_project.staging.project_id
      service = "serviceusage.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enable the required underlying Firebase Management API.
    resource "google_project_service" "staging_firebase" {
      provider = google-beta.no_user_project_override
    
      project = google_project.staging.project_id
      service = "firebase.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enable Firebase services for the new project created above.
    resource "google_firebase_project" "staging" {
      provider = google-beta
    
      project = google_project.staging.project_id
    
      # Wait until the required APIs are enabled.
      depends_on = [
        google_project_service.staging_serviceusage,
        google_project_service.staging_firebase,
      ]
    }
    
    # Create a Firebase Web App in the new project created above.
    resource "google_firebase_web_app" "staging" {
      provider = google-beta
    
      project      = google_firebase_project.staging.project
      display_name = "<DISPLAY_NAME_OF_YOUR_WEB_APP>"
      deletion_policy = "DELETE"
    }
    
  4. Run terraform apply to provision your new "staging" Firebase project and all its resources and enable its services.
  5. Verify that everything was provisioned and enabled as expected by checking them in the Firebase console as before.

Option 2: Reuse configurations with for_each

This option offers more code-reuse if each project should not differ in significant ways and you want to propagate changes to all projects at once. It uses the for_each meta-argument in the Terraform language.

  1. Open your main.tf file.
  2. Into each resource block that you want to replicate, add a for_each meta-argument, like so:

    main.tf
    # Create new Google Cloud projects.
    resource "google_project" "default" {
      provider        = google-beta.no_user_project_override
      name            = each.value
      # Create a unique project ID for each project, with each ID starting with <PROJECT_ID>.
      project_id      = "<PROJECT_ID>-${each.key}"
      # Required if you want to set up Authentication via Terraform
      billing_account = "<YOUR_BILLING_ACCOUNT_ID>"
    
      # Required for the projects to display in any list of Firebase projects.
      labels = {
        "firebase" = "enabled"
      }
    
      for_each = {
        prod    = "<PROJECT_NAME_OF_PROD_PROJECT>"
        staging = "<PROJECT_NAME_OF_STAGING_PROJECT>"
      }
    }
    
    # Enable the required underlying Service Usage API.
    resource "google_project_service" "serviceusage" {
      provider = google-beta.no_user_project_override
      for_each = google_project.default
    
      project = each.value.project_id
      service = "serviceusage.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enable the required underlying Firebase Management API.
    resource "google_project_service" "firebase" {
      provider = google-beta.no_user_project_override
      for_each = google_project.default
    
      project = each.value.project_id
      service = "firebase.googleapis.com"
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enable Firebase services for each of the new projects created above.
    resource "google_firebase_project" "default" {
      provider = google-beta
      for_each = google_project.default
    
      project = each.value.project_id
    
      depends_on = [
        google_project_service.serviceusage,
        google_project_service.firebase,
      ]
    }
    
    # Create a Firebase Web App in each of the new projects created above.
    resource "google_firebase_web_app" "default" {
      provider = google-beta
      for_each = google_firebase_project.default
    
      project      = each.value.project
      # The Firebase Web App created in each project will have the same display name.
      display_name = "<DISPLAY_NAME_OF_YOUR_WEB_APP>"
      deletion_policy = "DELETE"
    }
    
    
    # NOTE: For this codelab, we recommend setting up Firebase Authentication
    # using the Firebase console. However, if you set up Firebase Authentication
    # using Terraform, copy-paste from your main.tf the applicable blocks.
    # Make sure to add the `for_each` meta-argument into each block.
    
    
    # Copy-paste from your main.tf file the applicable resource blocks
    # for setting up Cloud Firestore (including rules) and
    # for setting up Cloud Storage for Firebase (including rules).
    # Make sure to add the `for_each` meta-argument into each block.
    
    You can find the complete configuration of a main.tf file that uses the for_each meta-argument in this codelab's GitHub repository:

    web/terraform-checkpoints/replicate-config/main-foreach.tf

    If you want to use this config, make sure you do the following:
    1. Copy the config from main-foreach.tf, and then paste it into your main.tf file.
    2. In your main.tf file, do the following:
      • In the google_project resource block, update the name attribute, the project-id attribute, and (if you set up Authentication via Terraform) the billing_account attribute with your own values.
      • In the google_firebase_web_app resource block, update the display_name attribute with your own value.
      • In the google_firestore_database and google_app_engine_application resource blocks, update the location_id attributes with your own value.
  3. Instead of applying this config right away, it's important to understand and fix a few things about how Terraform interprets this config compared to the existing infrastructure.
    1. Right now, if you applied this config that uses for_each, the resource addresses would look like the following:
      google_project.default["prod"]
      google_project.default["staging"]
      google_firebase_project.default["prod"]
      google_firebase_project.default["staging"]
      google_firebase_web_app.default["prod"]
      google_firebase_web_app.default["staging"]
      
      However, the existing project you created in the first part of this codelab is known to Terraform as the following:
      google_project.default
      google_firebase_project.default
      google_firebase_android_app.default
      
    2. Run terraform plan to see what actions Terraform would take given the current state.

      The output should show that Terraform would delete the project you created in the first part of this codelab and create two new projects. This is because Terraform doesn't know that the project at the address google_project.default has been moved to the new address google_project.default["prod"].
    3. To fix this, run the terraform state mv command:
      terraform state mv "google_project.default" "google_project.default[\"prod\"]"
      
    4. Similarly, to fix all the other resource blocks, run terraform state mv for google_firebase_project, google_firebase_web_app, and all the other resource blocks in your main.tf file.
    5. Now, if you run terraform plan again, it shouldn't show that Terraform would delete the project you created in the first part of this codelab.
  4. Run terraform apply to provision your new "staging" Firebase project and all its resources and enable its services.
  5. Verify that everything was provisioned and enabled as expected by checking them in the Firebase console as before.

12. Bonus step: Deploy your staging and prod apps

  1. In your app's codebase, change the firebase-config.js to use the Firebase config from your staging project instead.

    To remind yourself how to get your Firebase config and add it to your app, see the earlier step of this codelab, Add your Firebase configuration to your app.
  2. At the root of your web directory, run the following command to deploy your app to your staging Firebase project.
    firebase deploy --only hosting --project=<STAGING_PROJECT_ID>
    
  3. Open your staging app in the browser via the URL that's printed in the output of firebase deploy. Try signing in, sending messages, and uploading images.

    When you deploy an app to a Firebase project, it uses real Firebase resources, not emulated resources. As you interact with your staging app, you should see data and images appear in your staging project in the Firebase console.
  4. After testing your app in staging, change the firebase-config.js back to using the prod project's Firebase config (the first project that you created in this codelab).
  5. At the root of your web directory, run the following command to deploy your app to your production Firebase project.
    firebase deploy --only hosting --project=<PRODUCTION_PROJECT_ID>
    
  6. Open your production app in the browser via the URL that's printed in the output of firebase deploy. Try signing in, sending messages, and uploading images.

    You should see data and images appear in your production project in the Firebase console.
  7. When you're finished interacting with the two apps for this codelab, you can stop Firebase from serving them. Run the following command for each of your projects:
    firebase hosting:disable --project=<STAGING_PROJECT_ID>
    
    firebase hosting:disable --project=<PRODUCTION_PROJECT_ID>
    

13. Congratulations!

You've used Terraform to configure a real-time chat web application! And you've followed best practices for development environments by creating separate Firebase projects for staging and prod.

What we've covered

  • Using the Terraform CLI to manage cloud resources
  • Using Terraform to configure Firebase products (Authentication, Firestore, Cloud Storage, and Security Rules)
  • Running and testing a web app locally using the Firebase Local Emulator Suite
  • Importing Firebase into a web app
  • Using Terraform to replicate a configuration across multiple environments

For more information about Firebase and Terraform, visit our documentation. You can find a list of all Firebase products with Terraform support, sample Terraform configurations for common use cases, and helpful troubleshooting and FAQ.