Dev Log - Deploying a NestJs App to Cloud Run Pt. 2

Learn how to deploy a Google Cloud Run Service, Configure a Custom Domain, and store sensitive config data in Secret Manager

Dev Log - Deploying a NestJs App to Cloud Run Pt. 2

My Angular App for Social Sprinkler renders the HTML, but it's pretty useless for the purpose of being a landing page. Drawing interest from potential customers needs a call to action which collects an email address.

I've separated my Angular Universal Application and email services because as I move along in this project the email service requests may take longer to process I'm domaining out several other key services in which this email service will fall under my marketing domain.

Goals

1. Create a Docker Image for the Email Service

2. Stand up Service in Cloud Run

3. Configure a Custom Domain

Create a Docker Image for Email Service

The email API exposes an endpoint accepting an email address and a listId. The service runs on Node, but unlike my implementation with the Angular Universal App, the node modules need to be added to the image.

The time consuming process of creating the image involves installing all the modules. I shortened the amount of time by creating an image with the required dependencies. I plan to deploy the base image anytime the package.json changes.

FROM node:14-slim

RUN mkdir /app
WORKDIR /app
COPY ./package.json .
COPY ./package-lock.json .

RUN npm install --production

base.dockerfile - tagged as: mutable-ideas:0.0.1

Another Docker file for deployment of the application inherits the base Docker image created in the previous step. The Docker file then copies the built application to an /app directory, uses the same build arguments as used in Part 1, and starts the service.

FROM mutable-ideas:0.0.1

ARG NX_PROJECT_OUTPUT_DIR
ARG MAIN_JS
ARG PORT

ENV MAIN_JS ${MAIN_JS}
ENV PORT ${PORT}

COPY $NX_PROJECT_OUTPUT_DIR .
EXPOSE ${PORT}

CMD node ${MAIN_JS}

backend.dockerfile

Up Email Service in Cloud Run

The steps for building and pushing the image created in the prior step do not differ from Deploying an Angular Universal Application to Cloud Run. I ran the commands below to build my-backend-image-name:0.0.1.

# Build the Image
docker build -f docker/backend.dockerfile --build-arg NX_PROJECT_OUTPUT_DIR="./dist/apps/<output-path>" --build-arg MAIN_JS="./main.js" -t my-backend-image-name:0.0.1 .

Build the Docker Image for the Backend

Artifact Registry is listed below for reference for configuration and pushing the image.

Quickstart: Store Docker container images in Artifact Registry | Artifact Registry documentation | Google Cloud
Create a private repository to store your container images.

The following commands tag and push the Docker Image to Artifact Registry.

# Tag the image locally
docker tag <my-backend-image-name>:0.0.1 us-<region>-docker.pkg.dev/<project-name>/<grouping-folder>/<my-backend-image-name>:0.0.1

# Push the tagged image
docker push us-<region>-docker.pkg.dev/<project-name>/<grouping-folder>/<my-image-name>:0.0.1

Push Docker Image to Artifact Registry

Creating the Email API requires the secret to be made available to the container for Sendgrid.

Secret Manager

Navigating to the Secret Manager, I generated a text secret from the Sendgrid API key.

Secret Manager

The secret doesn't require more than the name of the secret and it's value.

Secret Form

I returned to Cloud Run to create a new service. In the Container tab, select Reference a Secret. I want to expose this as an environment variable to the container. With everything configured, I clicked Create.

In about a minute, the container spun up. I tested the default URL from Postman checking if a POST request accepted my email address for subscription to a list. YAHTZEE!

Configuring a Custom Domain

How many domains do you own that aren't in use? This time I actually have intentions of using SocialSprinkler.com. Until I deploy to production, I'm going to map my domain to a development subdomain. Once completed, certain routes will navigate to my services on the subdomain https://dev.socialsprinkler.com.

As I create separate services per my domain, the split of services respond to defined routes and what's exposed to the internet. Configuring routes to specific services in Cloud Run requires a load balancer.

I misconfigured domain mapping per my requirements the first time around. I mapped the custom domain to the front end using the ghs.google.com site by clicking the Map Custom Domain button. If you have separate services routed to different services, you'll need to remove this in DNS.

Configuring the Load Balancer

I navigated to the create load balancer choosing an Https Load Balancer.

Https Load Balancer

Then I named the load balancer.

Named the Load Balancer Form

The load balancer configuration requires a certificate, an external IP address. It can't be seen in the screen shot, but I also enabled the Http -> Https redirect.

I created a certificate example-certificate and an IP Address ss-fe-ip.

In the backend configuration, I mapped the two services to a Network Endpoint Group for Serverless.

Backend Service and Network Endpoint Group

I made sure to select the Backend Type to Serverless network Endpoint Group. You'll notice it calls out Cloud Run, App Engine, and others in the description. On selection, the New Backend section will list services available to map your service. I left the rest as defaults and clicked create.

Each service listens on a different route. The routes configuration determines which service handles the request based on a Host and a Path.

Cloud Run Routing Rules

I saved the configuration for the load balancer. In the final step, I mapped my custom domain to the IP address earlier.

After a few minutes, the Email Service and Angular Universal Application were running, and I was able to add an email address to my Sendgrid email list.

Conclusion

I created a base.docker file that required a deploy of a specific Docker image for my Email Service to Artifact Registry.

The Email Service in Cloud Run consumed secrets specific to a third party service that required an Api Key to make requests. The Api Key is confidential, so I used Secret Manager to store the key. Configuring the Cloud Run Service for email, I mapped the secret to an environment variable.

Multiple services exposed to the internet with a custom domain for Cloud Run required a Load Balancer to route requests to different services.