App Engine is Google Cloud's original platform-as-a-service offering. It has been around since 2008, and while Cloud Run has taken over for many use cases, App Engine still has its place - especially for applications that need automatic scaling, traffic splitting, and managed SSL without any container knowledge. The Standard environment starts up in milliseconds and scales to zero, while the Flexible environment runs containers on managed VMs.
Managing App Engine with Terraform is a bit different from most GCP resources. The App Engine application itself is a singleton - you can only have one per project, and it cannot be deleted once created. The location is permanent too. This makes getting the Terraform configuration right the first time particularly important.
App Engine Application Model in Terraform
The google_app_engine_application resource creates the application. Creating the App Engine Application with Terraform begins with this singleton resource.
Choose the location carefully because it affects latency for your users and determines where your data is stored.
The Standard environment scales to zero and starts in milliseconds, but it has restrictions on the runtime environment. The Flexible environment runs real containers but has a minimum of one instance always running.
The differences between the two environments impact Terraform planning and cost expectations.
| Feature | Standard Environment | Flexible Environment |
|---|---|---|
| Startup behavior | starts up in milliseconds | runs containers on managed VMs |
| Scaling | scales to zero | minimum of one instance always running |
| Runtime | restrictions on the runtime environment | runs real containers |
| Use case | automatic scaling, fast cold start | container workloads with always-on instances |
Managing versions with Terraform requires special care. When using Terraform with App Engine versions, set noop_on_destroy = true on version resources. Without this, Terraform will try to delete the version when you update, which can cause downtime.
Traffic splitting requires at least two deployed versions. Deploy the new version first, then update the traffic split.
Initial Terraform Setup for App Engine
A common starting point is to use community examples to bootstrap the provider and modules.
Name it something you can remember, and store it somewhere secure on your machine.
You supply the key to Terraform using the environment variable GOOGLE_APPLICATION_CREDENTIALS, setting the value to the location of the file, as follows:
bash
export GOOGLE_APPLICATION_CREDENTIALS={{path}}
- Clone the GitHub repository by running the following command on your terminal:
bash $ git clone https://github.com/bhidalto/terraform-appengine.git - Change directory to the examples folder to run any of the provided examples.
bash $ cd terraform-appengine/modules/examples/
Run
bash
terraform init
to download the different modules as well as the Google provider.
Optional steps for verification:
Run
bash terraform plan
to see the different resources that will be created.Run
bash terraform apply
to create the infrastructure within your Google Cloud Platform project.Run
bash terraform destroy
to undeploy the resources created.
Application Lifecycle Constraints and Removal
The App Engine application itself is a singleton - you can only have one per project, and it cannot be deleted once created. The location is permanent too.
Terraform does not currently support deletion of App Engine resources; although Terraform will show the resource as being destroyed, it will not actually delete the App Engine application. However, the App Engine application will no longer be managed by Terraform.
Removing the google_app_engine_application resource from configuration follows a specific plan flow.
If you have an existing google_app_engine_application resource in your Terraform configuration file, remove it from that file now.
Afterwards, once again run:
bash
terraform plan
You should see output similar to the following:
```
Terraform will perform the following actions:
googleappengine_application.app will be destroyed
(because googleappengine_application.app is not in configuration)
```
Once you are satisfied with the plan output, run
bash
terraform apply
Firestore and Datastore Unlinking Considerations
Previously, all Firestore in Datastore mode databases were linked to an App Engine app. Datastore mode databases are now provisioned unlinked from App Engine by default.
Previously, all Firestore databases were linked to an App Engine app. Firestore databases are now provisioned unlinked from App Engine by default.
Unlinking is a permanent operation. It may take up to five minutes for the unlinking operation to take effect.
When Terraform plans show a forced replacement, inspect the configuration.
If the output includes a line similar to:
google_firestore_database.database must be replaced
then inspect your Terraform configuration file to see if there were any mistakes, particularly in the project, location, or name fields, and then run terraform plan again. Any fields that are requiring Terraform to replace your database will be marked with # forces replacement in the plan output.
Once you are satisfied with the Terraform plan output, run:
bash
terraform apply
If the output shows any fields changing, ensure these changes are intended. If the output includes a line similar to:
google_firestore_database.database must be replaced
then inspect your Terraform configuration file to see if there were any mistakes, particularly in the project, location, or name fields, and then run terraform plan again. Any fields that are requiring Terraform to replace your database will be marked with # forces replacement in the plan output.
Migrating Firestore Management Away From App Engine
If you previously managed Firestore databases via the google_app_engine_application Terraform resource, you can use the google_firestore_database Terraform resource instead.
For general instructions on managing Firestore databases via Terraform, see Automating database creation.
Create a google_firestore_database resource
In your Terraform configuration file, create a new google_firestore_database resource:
hcl
firestore.tf
resource "google_firestore_database" "database" {
project = "project"
name = "(default)"
location_id = "location"
type = "database_type" // either "FIRESTORE_NATIVE" or "DATASTORE_MODE"
app_engine_integration_mode = "DISABLED" // Optional, but recommended for safety
delete_protection_state = "DELETE_PROTECTION_ENABLED"
}
See Firestore locations for the list of available locations. Choose the location corresponding to that of your existing database.
Import the existing Firestore database
First, ensure that the Firestore API is enabled.
Next, import the existing Firestore database into your Terraform state:
bash
terraform import google_firestore_database.database "(default)"
Next, run:
bash
terraform plan
Inspect the output to ensure the import completed successfully. If the output shows any fields changing, ensure these changes are intended.
Deploying Apps with Terraform Workflows
A practical workflow for deploying App Engine from a repository is to initialize and apply incrementally.
Make sure you are in deployappsto_gcp/ directory
- Deploy with terraform
bash cd .. terraform init terraform apply - Now let’s deploy the App Engine. Uncomment code in app-engine.tf and save the file
bash terraform apply
Wait for the deployment to finish. At the end terraform outputs the URL to an App Engine application. Click on the link to test the app
App Engine Versus Cloud Run in the Same Terraform Project
To use Cloud Run, you need to build a Docker image again and store it in Artifact Registry.
- Let’s first create an Artifact Registry repository named devops-repo. Uncomment code in artifact-registry.tf and save the file
- Deploy with terraform
bash terraform apply - Configure Docker to authenticate to the Artifact Registry Docker repository. I used europe-west1 as my region for all resources but if you changed it in provider.tf in previous step, update it here too
bash export REGION=europe-west1 gcloud auth configure-docker $REGION-docker.pkg.dev - Build the image for Cloud Run. This time we will use Google Cloud Build to submit a build and create a container image. Change directory to apps/cloud-run/
bash cd apps/cloud-run/ gcloud builds submit --tag $REGION-docker.pkg.dev/$DEVSHELL_PROJECT_ID/devops-repo/cloud-run-image:v0.1 . - Deploy Cloud Run, uncomment the code in cloud-run.tf, save it and run below command
bash cd ../../ terraform apply
Wait for the deployment to finish. At the end terraform outputs the URL to a Cloud Run application
Conclusion
App Engine remains a viable choice for applications that benefit from its built-in features - automatic SSL, traffic splitting, firewall rules, and dispatch routing. Terraform manages the application setup, firewall configuration, and domain mappings effectively. The version deployments are better handled through a CI/CD pipeline, but Terraform can bootstrap the initial deployment and manage the infrastructure around it.
Working with App Engine in Terraform requires acceptance of permanent decisions. The singleton application resource cannot be deleted, and the location is permanent. This makes early validation essential. Setting noop_on_destroy = true on version resources prevents accidental downtime during updates, and traffic splitting should always be performed by deploying a new version first and then shifting traffic.
Firestore and Datastore decoupling is now the default. Databases are provisioned unlinked from App Engine by default, and unlinking is permanent. Migrating existing state to google_firestore_database with app_engine_integration_mode = "DISABLED" provides safer, more explicit control and avoids forced replacements during plan.
Operational workflows benefit from clear separation between infrastructure and deployment. Terraform excels at bootstrapping the google_app_engine_application, enabling APIs, configuring firewall rules, and preparing service accounts. Version promotion and traffic splitting are safer in a CI/CD pipeline, with Terraform maintaining the surrounding infrastructure.