Using Cloud Run instead of GKE

Google Cloud Platform offers another service to operate containers called Cloud Run. It brings the idea of serverless computing to your containers.

Instead of being limited to the available support for languages, libraries and code restrictions in Cloud Functions you can create and run your own container in a similar, serverless fashion. Compared to Kubernetes Engine, Cloud Run abstracts the overhead of administration and enables you to deploy your application almost instantly.

Cloud Run is a managed compute platform that automatically scales your stateless containers. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications.

Find out more information about Cloud Run.

Taking the React app to Cloud Run

Similar to the deployment to Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) you would want to deploy the existing React app to Cloud Run.

The following command allows you to use any of the available containers in Container Registry (or Docker) and launch it on Cloud Run.

> gcloud run deploy --image gcr.io/alc-4-program/alc4cloud:cloudbuild

If you do not specify a service name, platform, region, or access mode the command is going to prompt you for the missing information.

Interactive service creation for Cloud Run

Here is the complete command with all mandatory switches for easier handling.

> gcloud run deploy alc4cloud \
  --image gcr.io/alc-4-program/alc4cloud:cloudbuild \
  --platform=managed \
  --region=us-central1 \
  --allow-unauthenticated

After a short while the command output is most probably going to respond with the following information.

...
Deployment failed
ERROR: (gcloud.run.deploy) Cloud Run error: Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable. Logs for this revision might contain more information.

Oh, what happened here?
Your container is in perfectly good shape but still Cloud Run rejects the deployment due to the inability to listen on the port defined by the PORT environment variable.

RTFM - Read The Fine Manual

According to the documentation on Building Containers for Cloud Run container images are accepted as long as they respect the container contract.

In particular, your code must listen for HTTP requests on the port defined by the PORT environment variable.

Okay, then let's have a look.

To learn more about the contract your containers must respect to be deployed to Cloud Run, see Container Contract.

In Cloud Run container instances, the PORT environment variable is always set to 8080, but for portability reasons, your code should not hardcode this value.

How is this related to your container running the React app?

Following the article Working with Docker (ALC 4.0 Cloud Challenge I) you wrote a Dockerfile to build containers using nginx webserver. Using the default configuration and listening on port 80.

That's a violation to the container contract as per specification and you have to change the implementation of the Docker image.

Modifying the container

In order to respect the container contract for Cloud Run you need to amend the existing Dockerfile slightly. There are two changes needed.

First, you have to change the Listen directive in the default configuration of nginx. An elegant approach would be to use sed - the stream editor for filtering and transforming text - in the Linux system used to run the React app.

Add the following RUN statement to the production-stage in the Dockerfile.

RUN sed -i 's/80/8080/g' /etc/nginx/conf.d/default.conf

Next, you have to change the exposed port of the container to grant access to it from external sources. Change the EXPOSE line like so.

EXPOSE 8080

The adjusted Dockerfile should have the following content.

# build stage
FROM node:lts-alpine as build-stage
RUN npm i -g react-scripts
WORKDIR /app
COPY package*.json yarn.lock ./
RUN npm ci
COPY . ./
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
RUN apk add --no-cache bash
COPY --from=build-stage /app/build /usr/share/nginx/html

COPY env.sh /usr/share/nginx/html
COPY .env /usr/share/nginx/html
RUN chmod +x /usr/share/nginx/html/env.sh
WORKDIR /usr/share/nginx/html

# Cloud Run requires port 8080. Let's change nginx...
RUN sed -i 's/80/8080/g' /etc/nginx/conf.d/default.conf
EXPOSE 8080

CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""]

Now, your local source is ready for Cloud Run.

Submit and Run

Either you would go forward to create the new container on your local system according to the article Working with Docker or you might like to send the sources as described in Working with Cloud Build.

Being smart you submit to Cloud Build using this command.

> gcloud builds submit -t gcr.io/alc-4-program/alc4cloud:cloudrun .

Although it is the same React app you should give the container image a new tag, here cloudrun to differentiate it a little bit from the previously created images.

Note: I put a file called Dockerfile.Cloudrun into my repository on GitHub for reference. Unfortunately, you cannot specify a different file when submitting to Cloud Build. You might have to rearrange the files if you want to use it.

After successful submission to Cloud Build your Container Registry might look a little like the following.

The newly created image for Cloud Run is ready to rumble

Reflecting all tagged images built so far.

With the containerized image available in the registry it's now time to deploy to Cloud Run using the gcloud run command.

> gcloud run deploy alc4cloud \
  --image gcr.io/alc-4-program/alc4cloud:cloudrun \
  --platform=managed \
  --region=us-central1 \
  --allow-unauthenticated \
  --set-env-vars=SITE_URL="https://jochen.kirstaetter.name",SITE_STATUS="Cloud Run"

Note: Amend the tag value according to the image you built. Setting the environment variables might not be necessary depending on your implementation.

The deployment is going to take a few minutes. Maybe you like to stretch your legs, grab a glass of water, and have a cereal bar to re-energize yourself while waiting.

Successful deployment of a service revision on Cloud Run

Cloud Run will respond with a simple Done. and provides you with the URL to run your stateless container on the web.

React app running on Cloud Run

Awesome, well done!
You have managed to adjust the Docker image to listen to port 8080 and deployed the container to Cloud Run.

Same steps in Cloud Console

In case that you feel more comfortable in Cloud Console than typing commands in the terminal, you can navigate to Cloud Run in the sidebar menu. There you click on Create Service to set up a new service operating on Cloud Run.

Choose your container image to create a new service on Cloud Run

Similar to the gcloud command you specify the container image, the preferred platform to deploy inclusive a location, give the service a name and choose the authentication mode.

Then you hit the Create button and wait until the service has been created.

Work in progress: Creating the service on Cloud Run

Shortly after the endpoint will be accessible via exposed URL and the service metrics are shown in Cloud Console.

The service is operational and accessible

The choice is yours. Remember that the container image has to respect the container contract and listen to port 8080. Otherwise, deployment is going to fail.

Pricing of Cloud Run

At the time of writing the following price structure applies to Cloud Run.

Cloud Run free tier provides enough resources to run a personal site, i.e. a blog

A small project like the React app could be an interesting candidate to deploy and run on Cloud Run. Depending on the number of visitors and hence invocations per month it might be very affordable, too.

Cloud Run has HTTPS built-in

Each Cloud Run service gets an out-of-the-box stable HTTPS endpoint, with TLS termination handled for you.

Cloud Run comes with HTTPS URLs out-of-the-box

Using your own domain

You might have seen that the auto-generated URL Cloud Run allocates to your service isn't the most user-friendly. Conveniently, Cloud Run offers custom domains out-of-the-box.

On the Cloud Run service overview click on the triple dot button and select Manage Custom Domains to map your own domain for your service.

Cloud Run can map your services to your own domains

Next, click on Add Mapping in case that you are not greeted by the Add mapping dialog already.

Choose a custom domain for your service

In case that your domain is not listed as a verified domain you are offered to do it on the spot.

Optionally, your domain needs to be verified, if not already

Wait until the verification process has been completed successfully and then specify the domain to use.

Map your service to your custom domain

Cloud Run is able to map your service to the base domain as well as to a subdomain record.

Update the DNS records of your custom domain

Finally, Cloud Run gives you details on how to update the DNS records of your custom domain. This step completes the mapping to your service.

It takes about five to six minutes until the domain mapping is done. However, give it some more time until the Cloud Run service responds to your custom domain as expected. See for yourself here: https://alc4cloud.kirstaetter.name/

Service on Cloud Run accessible via custom domain

And you're done, again.

Check out the documentation on Mapping custom domains for more details and background information on this matter.

Enable Cloud Run API

The Cloud Run API is not enabled by default in your GCP project. The gcloud command will prompt you accordingly and allows you to activate it.

Confirm the question with Y and wait as instructed to retry the submission.

Of course, you could also navigate around in APIs & Service > Library in the Cloud Console. Enable both Cloud Build and Cloud Run APIs.

Enable Cloud Build API and Cloud Run API

Using Cloud Run as a substitute for Kubernetes Engine has its charm. It offers a faster path to develop, deploy and operate with less administrative overhead. Additionally, Cloud Run provides a richer environment for your applications based on your custom-made stateless container compared to the pre-defined language runtime provided by Cloud Functions.

Apart from adjusting the container to listen to the default port 8080 as per container contract and optionally mapping a custom domain it is an almost effortless workflow to deploy a container image to Cloud Run and make it publicly accessible.

What do you think? Do you have use-cases where Cloud Run is the right choice for you? Leave a comment below and start the conversation with me and other readers.

Image credit: Fitsum Admasu