A manual build and deploy of App Engine using Cloud Build

How to submit and build with Cloud Build when gcloud app deploy doesn't work

One of our customers uses Google App Engine to host an Angular app with Angular Universal (server side rendering or SSR) enabled. SSR was enabled to overcome SEO issues.

Typically the deployment is straightforward. The package.json contains the following scripts:

{
  "name": "angular-appengine-ssr-app",
  "version": "1.0.0",
  "scripts": {
    "deploy": "npm run build:ssr && gcloud app deploy",
    "build:ssr": "npm run build:client-and-server-bundles",
    "build:client-and-server-bundles": "ng build --prod --aot && ng run ssr-app:server:production"
  }
}

So all they have to do is run this on the command line:

$ npm run deploy

Essentially, after building, the CLI command gcloud app deploy will deploy it to the App Engine. It can be a long wait, as a lot happens in the background:

New Node.js version in Cloud Build

Since August 2022, the customer started experiencing problems with deploying the app. The CLI keeps returning tons of error messages and deployments failed. The customer could not figure out the problem and finally asked my team for help.

I dug into the Cloud Build history to invest the logs. After hours of investigation and experimenting, I concluded that it was a Node.js version problem. The Angular app has been running well with node v16.15.0. In Cloud Build, it started using v16.16.0 in August 2022. This conflicted with a lot of packages: image.png

The first solution was to try upgrading to v16.16.0. This created a lot more problems as many of the packages had dependencies that simply could not be upgraded. I wasn't keen to go down the rabbit hole of upgrading every single package.

A manual submit to Cloud Build

My next solution was to force Cloud Build to use v16.15.0. This was not simple unfortunately. Passing in the version number via parameters to the gcloud app deploy command doesn't work.

I eventually found that I can use gcloud builds submit instead and pass in the version number in the parameters. I also found that the build URL is always the latest and can be specified by the latest tag:

gcloud builds submit --pack=env=GOOGLE_RUNTIME_VERSION=16.15.0,image=${URL}

I thought this would work but it still failed when I tried gcloud app deploy after the build. I was finally rewarded with the final piece of the puzzle after reading through the documentation again. The --image-url parameter can only be used for the App Engine flex environment and it was simple change in the app.yaml from standard environment to flexible environment:

runtime: nodejs
env: flex

The updated deployment script is now:

{
  "name": "angular-appengine-ssr-app",
  "version": "1.0.1",
  "config": {
    "gae_img": "asia.gcr.io/project-id/app-engine/app/default/ttl-18h:latest"
  },
  "scripts": {
    "deploy:nodejs16-15-0": "npm run build:ssr && npm run gcloud:builds-submit && npm run gcloud:app-deploy",
    "build:ssr": "npm run build:client-and-server-bundles",
    "build:client-and-server-bundles": "ng build --prod --aot && ng run ssr-app:server:production",
    "gcloud:builds-submit": "gcloud builds submit --pack=env=GOOGLE_RUNTIME_VERSION=16.15.0,image=${npm_package_config_gae_img}",
    "gcloud:app-deploy": "gcloud app deploy app-flex.yaml --image-url=${npm_package_config_gae_img}"
  }
}

UPDATE: as of November 2022, Cloud Build is using v16.18.0 but I can see that this method is still working!