Sharing Secret Environment Variables with Google Cloud Build and Angular Universal

If you deploy your Angular Application to a Google Cloud Run Container, you should do it through Github Actions and Google Cloud Build. This is what creates the CI/CD (Continuous Integration and Continuous Delivery) deployments easily.

Obviously this makes more sense for Angular Universal than Angular, since you need a node server or container to run the former.

Local Version

Angular Universal is not made to use the dotenv package, despite what articles tell you:

Bonus Tip - Right Click - Open in New Private Window on all these medium articles that won't display... that is the reason I love dev.to!

Getting it to work is a pain in the butt. So, why not just use simple JSON!?

  1. Create environment.prod.json and environment.json in your src/environments folder
  2. Ignore the json files:

.gitignore

# Config Files
/src/environments/*.json

3. Update your environment.ts files

import * as firebase_env from './environment.prod.json';

export const environment = {
  production: true,
  firebase: firebase_env
};

4. Add the appropriate JSON data to the environment.prod.json file(s).

{
  "key": "value",
  ...
}

Notice you need to have quotes in the key.

5. Enable JSON imports

Edit tsconfig.json and add

{
  "compilerOptions": {
    ...
    "resolveJsonModule": true,
    ...
}

Deployment

This post is not about Deployment itself. You need to know the basics of Cloud Run, Cloud Container Registry, Github Actions, and Cloud Build.

That being said, you don't have to understand much but how to enable them.

Here are a few links for that:

Cloud Build

Cloud Build pretty much does everything for you. You can get Github Actions working through Github, but it is much easier to go through Cloud Build.

Basically go to Cloud Build, make sure it is enabled, click Setup Build Trigger, name it, and chose your repository. You will be prompted to login to Github, allow access etc.

You may need to change the main branch to master depending on your setup.

YAML File

You can create your own cloudbuild.yaml, but you don't have to. It is done automatically for you once you connect your repository.

Once you have all this configured, click the Open Editor button.

There, you can modify your inline YAML file. Now add the build-arg to the build step like so:

steps:
  - name: gcr.io/cloud-builders/docker
    args:
      - build
      - '--build-arg'
      - _FIREBASE=$_FIREBASE
      - '--no-cache'
      - '-t'
      - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
      - .
      - '-f'
      - Dockerfile
    id: Build

In this case, I am adding the _FIREBASE variable which will be passed to the docker file.

You may also need to add timeout: 1600s to the bottom of the file if you believe the build will take more than 10 minutes. Angular Builds can take a while due to all the optimizations etc.

Now, click DONE

Add your Secret Variable as you want. Since we are using JSON, make sure the key is wrapped in quotes.

Dockerfile

Finally, use this Dockerfile if you use Angular Universal

FROM node:14-slim
ARG _FIREBASE
WORKDIR /usr/src/app
COPY . .
RUN npm i
RUN npm audit fix
RUN echo "$_FIREBASE" >> src/environments/environment.prod.json 
RUN npm run build:ssr
CMD ["npm", "run", "serve:ssr"]

Basically, it creates the environment file from the substitution variable. You need to add the ARG for this to work. You can do any variables you like.

I have not seen any other tutorial do it quite like this, so this is a unique approach, and much simpler IMHO than using .env files.

Client API Keys? Why do we need to do all this?

If you read the correct answer on stackoverflow, officially it explains that you don't need to do any of this. Your client API key is going to be viewable, or gettable, as your frontend source is not secure. You can run firebase commands in console.log, and do way more see Fireship Video. If you use Supabase, Firebase, or any API with a client key, it is hackable. Firebase should be secured with App Check and Security Rules, Supabase with Row Level Policies, etc, etc.

That being said, I still don't want someone easily copying and pasting my configuration code into their project on every fork or clone. Another layer of security is always good.

If your project is a backend project, this is especially good.

If you have a professional APP, you may want to look into Cloud Run CDN, which is Vercel's Edge equivalent. See here.

Hope this helps someone,

J

47