Google Cloud Build - firebase deployment error: "public directory 'dist/browser' does not exist, can't deploy hosting to site PROJECT-ID"

Viewed 590

I´m trying to simultaneously deploy my Angular Universal web-app to Cloud Run (for SSR) and Firebase Hosting (for caching). The deployment to Cloud Run works fine and I followed this guide (https://cloud.google.com/build/docs/deploying-builds/deploy-firebase#using_the_firebase_community_builder) to enable Firebase deployment with Google Cloud Build.

However, whenever I run gcloud builds submit --config=cloudbuild.yaml the error: public directory 'dist/browser' does not exist, can't deploy hosting to site PROJECT-ID is thrown during the Firebase deployment step in the cloudbuild.yaml.

cloudbuild.yaml:

steps:
  # build image
  - name: 'gcr.io/cloud-builders/docker'
    args: [ 'build', '-t', 'gcr.io/PROJECT-ID/SERVICE-ID', '.' ]
  # push image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/PROJECT-ID/SERVICE-ID']
  # deploy to Cloud Run
  - name: "gcr.io/cloud-builders/gcloud"
    args: [ "run", "deploy", "SERVICE-ID", "--image", "gcr.io/PROJECT-ID/SERVICE-ID", "--region", "us-central1", "--platform", "managed", "--allow-unauthenticated" ]
  # deploy to Firebase (ERROR HAPPENS HERE Specified public directory 'xx' does not exist, can't deploy hosting to site PROJECT-ID)
  - name: gcr.io/PROJECT-ID/firebase
    args: ['deploy', '--project=PROJECT-ID', '--only=hosting']
images:
  - 'gcr.io/PROJECT-ID/SERVICE-ID'
timeout: 1800s
substitutions:
  _ENV_VARIABLE: development

Dockerfile

FROM node:14

# set work directory
WORKDIR usr/src/app

# copy package.json and package-lock
COPY package*.json ./

# install dependencies
RUN npm install

# copy local code to container
COPY . .

# build app
RUN npm run build:ssr

# serve app
CMD ["npm", "run", "serve:ssr"]

firebase.json

{
...
  "hosting": [
    {
      "public": "dist/browser",
      "ignore": [
        "**/.*"
      ],
      ...
    }
  ]
...
}

error-log

DEBUG: https://storage.googleapis.com:443 "GET /987290120943.cloudbuild-logs.googleusercontent.com/log-93082fbf-1b87-499a-9132-fed5f1c06aad.txt HTTP/1.1" 206 290
DEBUG: Reading GCS logfile: 206 (read 290 bytes)
Step #3:
Step #3: ←[1m←[37m===←[39m Deploying to 'PROJECT-ID'...←[22m
Step #3:
Step #3: ←[1m←[36mi ←[39m←[22m deploying ←[1mhosting←[22m
Step #3:
Step #3: ←[1m←[31mError:←[39m←[22m Specified public directory 'dist/browser' does not exist, can't deploy hosting to site PROJECT-ID
DEBUG: https://cloudbuild.googleapis.com:443 "GET /v1/projects/PROJECT-ID/locations/global/builds/93082fbf-1b87-499a-9132-fed5f1c06aad?alt=json HTTP/1.1" 200 None
DEBUG: https://storage.googleapis.com:443 "GET /987290120943.cloudbuild-logs.googleusercontent.com/log-93082fbf-1b87-499a-9132-fed5f1c06aad.txt HTTP/1.1" 206 120
DEBUG: Reading GCS logfile: 206 (read 120 bytes)
Finished Step #3
ERROR
ERROR: build step 3 "gcr.io/PROJECT-ID/firebase" failed: step exited with non-zero status: 1
DEBUG: https://storage.googleapis.com:443 "GET /987290120943.cloudbuild-logs.googleusercontent.com/log-93082fbf-1b87-499a-9132-fed5f1c06aad.txt HTTP/1.1" 416 168
DEBUG: Reading GCS logfile: 416 (no new content; keep polling)

Since everything works fine locally, I assume that it may have to do something with gcr.io/PROJECT-ID/firebase being in a different container than the service gcr.io/PROJECT-ID/SERVICE-ID where perhaps the dist folder is created.

Edit #1:

I tested creating a folder called "static" in the root of the project and changed the public property in firebase.json to 'static'. It worked which makes me believe that for some reason the dist/browser directory does not exist during the "deploy to Firebase" step in the cloudbuild.yaml even though it is created during RUN npm run build:ssr of the Dockerfile.

Edit #2:

I found something not quite adequate (https://stackoverflow.com/a/64215324/8581106) on a similar problem. However, the suggestion seems to be to also run the build command npm run build:ssr before the Firebase deployment in the cloudbuild.yaml to make sure the dist/browser directory exists. But I´d really like to avoid serving one build on Cloud Run and deploy a new build to Firebase, because Angular uses unique hashes for .js and .css files everytime and I feel like this could lead to caching problems.

Edit #3:

I created an image to visualize my problem better (see image below). I´m new to docker and Cloud Build, but I feel like this is the procedure when I run gcloud builds submit --config=cloudbuild.yaml. I don´t understand why the dist/browser exists in the Dockerfile scope but not in the cloudbuild.yaml scope.

procedure when creating container

1 Answers

According to https://stackoverflow.com/a/66714846/8581106, I can not access the dist/browser directory within the cloudbuild.yaml, because the directory is created in a different context through the Dockerfile.

Currently, the only solution I found was to deploy an empty folder to Firebase Hosting immediately after deploying the build to Cloud Run. As I understand it, the user now makes a Request to Firebase Hosting and gets the Response if it is already cached on the Firebase Hosting server. If the requested data is not cached the rewrites in the firebase.json allow to get the rendered data from Cloud Run and caches it afterwards if possible.

Although it seems to work, I don´t know if the files from Cloud Run are correctly cached on Firebase Hosting and in addition, the Lighthouse scores in terms of performance are way worse because of the missing compression for texts, images and files which was automatically managed by Firebase Hosting before. Still, at the moment this is the most viable solution.

Related