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
- A terminal/console
- The IDE/text editor of your choice, such as WebStorm, Atom, Sublime, or VS Code
- The browser of your choice, such as Chrome
- The Google Cloud CLI (gcloud CLI) - install this CLI and log in using a user account or a service account
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
- In the codebase of the downloaded sample app, navigate to the root of the
web
directory. - 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
- First, provision the underlying Google Cloud project.
To yourmain.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 thename
value is only used within Firebase interfaces and isn't visible to end-users. Theproject_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" } }
- 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 yourmain.tf
config file (just under the block that creates the new Cloud project), add the following resource block:
main.tf 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... # 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 }
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
- 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 beweb
):terraform apply
- In the terminal, Terraform prints a plan of actions it will perform.
If everything looks as expected, approve the actions by enteringyes
.
main.tfTerraform 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.
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
- To provision the new resource, run the following command from the root of the same directory as your
main.tf
file (which should beweb
). 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 theterraform apply
.tf
file and make any changes that it finds. - 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:
- In the Firebase console, locate the Build section in the left panel.
- Click Authentication, click Get started, and then click the Sign-in method tab (or click here to go directly there).
- Click Add new provider and, from the Additional providers section, select Google.
- Activate the Enable toggle.
- Set the public-facing name of your app to something like
FriendlyChat
(this doesn't need to be globally unique). - Choose a Project support email from the dropdown menu, and then click Save.
- You should see Google as an enabled sign-in provider.
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
- 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.
- To enable billing in your project via Terraform, add a
billing_account
attribute into the existinggoogle_project
resource in yourmain.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
- 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, ] }
- 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.
- Since this is your first time creating a client ID for this project, you need to configure your OAuth consent screen.
- Open the OAuth consent screen page, and then select the project you just created.
- Set the User Type to External, and then click Create.
- 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.
- Set the public-facing App name of your app to something like
- 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.
- Set up an OAuth client in the Credentials page by doing the following:
- Click Create credentials and select OAuth client ID.
- From the Application type drop-down, select Web application.
- In the Name field, enter the name of your app, for example
FriendlyChat
(this doesn't need to be globally unique). - 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 inmain.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 inmain.tf
.
- Under Authorized JavaScript origins, click Add URI and enter
- Click Save.
- 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
- 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 beweb
):export TF_VAR_oauth_client_secret="<YOUR_OAUTH_CLIENT_SECRET>"
Note that runningterraform apply
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. - Review the printed plan of actions. If everything looks as expected, type
yes
and press Enter to approve the actions.
Validate the changes
- In the Firebase console, locate the Build section in the left panel.
- Click Authentication, and then click the Sign-in method tab (or click here to go directly there).
- You should see Google as an enabled sign-in provider.
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.
- 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 ] }
- 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 useus-east1
(South Carolina) or use the region closest to you (see Cloud Firestore locations). - 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 filefirestore.rules
, which you can find at the root of theweb
directory. - 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.
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 ] } }
- Create a ruleset of Firebase Security Rules from the local
- Run
terraform apply
to provision the Firestore database and deploy its security rules. - Verify that the database is provisioned and that its security rules are deployed:
- In the Firebase console, locate the Build section in the left panel.
- Go to the Firestore Database section, and then click the Rules tab.
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.
- 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 thegoogle_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 }
- 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 filestorage.rules
, which you can find at the root of theweb
directory. - 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.
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 ] } }
- Run
terraform apply
to provision the default Cloud Storage bucket and deploy its security rules. - Verify that the bucket is provisioned and that its security rules are deployed:
- In the Firebase console, locate the Build section in the left panel.
- Go to the Storage section, and then click the Rules tab.
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.
- 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>
- 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.
- Open a new terminal window.
- Make sure you're at the root of the
web
directory. - Run
npm install
to download the Firebase SDK. - Run
npm update
to update any dependencies. - 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
- In the Firebase console, go to your Project settings.
- Scroll down to the Your apps card, and then select your web app.
- Select Config from the Firebase SDK snippet pane, and then copy the config snippet.
- 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.
- In your
main.tf
file, find yourgoogle_firebase_web_app
resource block (the block that registered a web app with your project). - 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", "") } } ...
- Since the
data
block and theoutput
block aren't intended for modifying the infrastructure in any way, you only need to run the following commands.- To load the Firebase config of your web app into the Terraform state of your directory, run this command:
terraform refresh
- To print the Firebase config values, run this command:
The following is an example output of a config. Your printed output will contain your project and app's values.terraform output –json
{ "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" } } }
- To load the Firebase config of your web app into the Terraform state of your directory, run this command:
- Copy the values from within the
value
map. - 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:
...
const firebaseAppConfig = getFirebaseConfig();
initializeApp(firebaseAppConfig);
Try out your app
Now that everything is configured for Firebase, you can try out your functional web app.
- Refresh the browser serving your app.
- 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.
- At the root of your
web
directory, create a new Terraform config file calledmain_staging.tf
. - Copy all the resource blocks from your
main.tf
file (except for theterraform
andprovider
blocks), and then paste them into yourmain_staging.tf
file. - 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"
toresource "google_project" "staging"
. - Resource references: Update each one. For example, update
google_firebase_project.default.project
togoogle_firebase_project.staging.project
.
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:- Copy the config from
main_staging-copypaste.tf
, and then paste it into yourmain_staging.tf
file. - In your
main_staging.tf
file, do the following:- In the
google_project
resource block, update thename
attribute, theproject-id
attribute, and (if you set up Authentication via Terraform) thebilling_account
attribute with your own values. - In the
google_firebase_web_app
resource block, update thedisplay_name
attribute with your own value. - In the
google_firestore_database
andgoogle_app_engine_application
resource blocks, update thelocation_id
attributes with your own value.
- In the
# 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" }
- Resource labels: Use a new name to avoid conflict. For example, rename
- Run
terraform apply
to provision your new "staging" Firebase project and all its resources and enable its services. - 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.
- Open your
main.tf
file. - Into each resource block that you want to replicate, add a
for_each
meta-argument, like so:
main.tf You can find the complete configuration of a# 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.
main.tf
file that uses thefor_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:- Copy the config from
main-foreach.tf
, and then paste it into yourmain.tf
file. - In your
main.tf
file, do the following:- In the
google_project
resource block, update thename
attribute, theproject-id
attribute, and (if you set up Authentication via Terraform) thebilling_account
attribute with your own values. - In the
google_firebase_web_app
resource block, update thedisplay_name
attribute with your own value. - In the
google_firestore_database
andgoogle_app_engine_application
resource blocks, update thelocation_id
attributes with your own value.
- In the
- Copy the config from
- 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.
- Right now, if you applied this config that uses
for_each
, the resource addresses would look like the following: However, the existing project you created in the first part of this codelab is known to Terraform as 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"]
google_project.default google_firebase_project.default google_firebase_android_app.default
- 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 addressgoogle_project.default
has been moved to the new addressgoogle_project.default["prod"]
. - To fix this, run the
terraform state mv
command:terraform state mv "google_project.default" "google_project.default[\"prod\"]"
- Similarly, to fix all the other resource blocks, run
terraform state mv
forgoogle_firebase_project
,google_firebase_web_app
, and all the other resource blocks in yourmain.tf
file. - 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.
- Right now, if you applied this config that uses
- Run
terraform apply
to provision your new "staging" Firebase project and all its resources and enable its services. - 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
- 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. - 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>
- 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. - 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). - 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>
- 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. - 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.