No servers, no problem: A guide to deploying your React application on Cloud Run

umangak
Staff

react-cloud-run.png

Launching your React project as a fully accessible live application doesn’t have to be a challenge. Ever wondered how you could turn your app code into a universally accessible service, all without getting tangled in server configurations? If yes, then you're in the right place.

This article is a beginner-friendly guide that unveils the entire process of containerizing and serving your React application with ease on Cloud Run, a serverless platform that takes away the overhead of managing servers.

Visualize your React frontend and your versatile backend, whether it's Python or another language, all coming together in a fault-tolerant deployment strategy that's robust, yet surprisingly simple to manage.

Dive into this comprehensive step-by-step tutorial on containerizing and deploying your React app using Google Cloud Run. We assume a common tech stack for our example: React.js for the frontend and Python for the backend.

In just a few minutes, you'll learn how to bring your application from your local machine to global availability on the internet, all through the seamless serverless solution offered by Google Cloud Run.

We'll start with some background concepts, architecture, and the approach before diving into the detailed step-by-step deployment instructions, complete with reusable code snippets and necessary commands.

Background and context

Cloud Run

Cloud Run is a Google service that streamlines the process of deploying your applications in minutes. It seamlessly runs your containerized app on Google's cloud infrastructure, handling scaling and management tasks, allowing you to focus on coding without the hassles of server administration.

Cloud Run is ideal for projects like proof-of-concepts, because you can deploy quickly and share a link with customers for immediate feedback. In essence, Cloud Run empowers developers to code more and worry less about infrastructure management.

We use Cloud Run to deploy our React app efficiently, eliminating concerns about the underlying system.

React JS

React JS is a JavaScript library for building user interfaces based on components. We selected React JS for our app's frontend for its modular structure, which simplifies updates and maintenance, and enhances the user experience. Its maintainability and flexibility also translate into time and cost savings.

Deployment

Deployment in software development refers to the process of making the application available to the end users. Deployment is crucial because it enables the app to be utilized in a live environment, allowing real users to interact with it.

Containerization/Dockerization 

Dockerization is the process of packaging an application along with its required libraries, frameworks, and dependencies into a "Docker Container". This helps to eliminate the infamous "it works on my machine" syndrome and aligns with the philosophy of "build once, deploy anywhere" - thus, making the overall development process more robust.

Approach and architecture

Approach

The initial step is to create the Docker file for our backend, and build the Docker image. These packaged software enables applications to run quickly and efficiently. By pushing these Docker images into the Artifact Registry, we gain a secure and scalable storage solution for our Docker images, conveniently in the Google Cloud. 

Following this, the next phase involves the creation of a new Cloud Run service. Cloud Run is serverless; it abstracts away all infrastructure management and allows us to focus on writing effective applications. After successfully deploying the backend Docker image in the service, we would be provided with a URL.

This URL replaces the original backend call in the frontend code, ensuring our frontend leverages the cloud-hosted backend services for its operations.

We repeat the Docker process for our frontend file i.e., create a Docker image on the frontend, push it to the Artifact Registry, and then finally create and deploy a Cloud Run service. Upon successful deployment, we will be provided with a URL. This returned URL will serve as the final URL of the application, signifying a successful deployment of the entire application in Cloud Run. 

This systematic process ensures the efficient deployment of both frontend and backend services of the application into the cloud, enhancing its accessibility and scalability.

Architecture

In this architecture, we have a bifurcated setup where the React-based front end and the corresponding backend are separately containerized and deployed as distinct services on Cloud Run - a serverless platform designed for running stateless containers. Here, the frontend delivers the user interface, providing a responsive and dynamic experience, while the backend manages business logic and databases.

Intercommunication between the frontend and the backend is governed by web security policies, including Cross-Origin Resource Sharing (CORS). CORS is a mechanism that adds HTTP headers to tell browsers to allow a web application running at one origin to access selected resources from a different origin. This is crucial for web applications where the frontend and backend are served from different origins, such as different domains, protocols, or ports. CORS authentication is vital to enable authorized cross-origin requests from the frontend to the backend, overcoming blocks by browsers' same-origin policy. This security measure facilitates data exchange between different domains and is implemented in the code where the request is being received.

When the React app (frontend) sends HTTP requests to the backend, it may include parameters that provide context or data necessary for the backend's operations, such as user data or tokens. Upon receiving a request, the backend processes the relevant logic, interacts with databases or other services as required, and then responds to the frontend with the results of the request - which could be data, confirmation messages, error codes, or other status information pivotal to the frontend's subsequent actions.

These interactions are crucial for a responsive and functional application, maintaining a lightweight frontend and a robust backend. The backend's response headers, including those related to CORS, instruct browsers to permit this essential communication, ensuring the integrity of the application.

Fig 1: ArchitectureFig 1: Architecture

Why should we deploy the frontend and backend separately? 

We will be deploying the frontend and backend separately, which is a fault tolerant approach with several advantages: 

  • Fault tolerance: Deploying the frontend and backend separately on Cloud Run ensures that an issue in one component doesn't bring down the whole application. The frontend can still serve static content even if the backend is facing issues - maintaining a level of user access.
  • Scalability: This architecture allows for independent scaling. The frontend can be scaled up to manage high traffic smoothly, while the backend can scale down during quieter periods, optimizing resource use and costs.
  • Development flexibility: Independent deployment enables parallel development and testing of the frontend and backend. Developers can work on their respective components without interfering with each other's progress.
  • Enhanced security: Separating the frontend and backend can enhance security by reducing the attack surface area. The backend can be protected behind a firewall or API gateway, while the frontend remains accessible to users. 

Overall, deploying the frontend and backend separately in Cloud Run is a recommended best practice for building scalable, resilient, and secure web applications. It promotes modularity, independent development, and efficient resource utilization.

Deployment

Step 1: Dockerize your code

1.1: Dockerizing your React frontend

Begin the process by creating a Dockerfile in the root directory of your React application's code. This file outlines the environment and commands necessary to run your app within a Docker container, allowing it to be deployed consistently across any Docker-supported environment.

A Docker container is a lightweight, standalone package that includes everything necessary to run a piece of software, including the code, runtime, system tools, libraries, and settings. The provided code packages your app into an image that's ready to run across any environment supported by Docker, without further configuration. Below is a reusable Dockerfile tailored for most React apps.

Create a file named `Dockerfile` without any extension in the root folder of your frontend project and add the following content: 

Dockerfile:

 

# Use the slim version of the node 14 image as our base
FROM node:14-slim

# Create a directory for our application in the container 
RUN mkdir -p /usr/src/app

# Set this new directory as our working directory for subsequent instructions
WORKDIR /usr/src/app

# Copy all files in the current directory into the container
COPY . .

# Set the PYTHONPATH environment variable, which is occasionally necessary for certain node packages
# 'PWD' is an environment variable that stores the path of the current working directory
ENV PYTHONPATH=${PYTHONPATH}:${PWD}

# Set the environment variable for the application's port
# (Be sure to replace '4200' with your application's specific port number if different)
ENV PORT 4200

# Install 'serve', a static file serving package globally in the container
RUN npm install -g serve

# Install all the node modules required by the React app
RUN npm install
# Build the React app
RUN npm run build

# Serve the 'build' directory on port 4200 using 'serve'
CMD ["serve", "-s", "-l", "4200", "./build"]

 

Let’s break down what each line in the Dockerfile does:

  • `FROM node:14-slim`: This line instructs Docker to pull the slim version of image 14 from the official Docker Hub registry. The slim image is preferable as it's lightweight and thus faster to pull and run.
  • `RUN mkdir -p /usr/src/app`: Creates a directory in the container for your app files.
  • `WORKDIR /usr/src/app`: Sets the newly created directory as the working directory for all subsequent commands.
  • `COPY . .`: Copies the current directory's contents on the host into the working directory in the container.
  • `ENV PYTHONPATH=${PYTHONPATH}:${PWD}`: Adds the current working directory to the `PYTHONPATH`. This is usually not necessary for a React application but sometimes required for certain build scripts.
  • `ENV PORT 4200`: Defines the environment variable `PORT`, which specifies the port number on which the app will run inside the container.
  • `RUN npm install -g serve`: Installs the static file server 'serve' globally in the image.
  • `RUN npm install`: Installs your app's npm dependencies as defined in your `package.json`.
  • `RUN npm run build`: Builds your app for production to the `build` folder.
  • `CMD ["serve", "-s", "-l", "4200", "./build"]`: Defines the command to launch the React app using the 'serve' package on the specified port.

This Dockerfile provides a general template for containerizing React applications. Developers can take this as a starting point and customize environment variables, base images, or commands according to their specific application’s requirements. Remember to test locally to ensure that the Docker container behaves as expected before proceeding to deploy.

1.2: Dockerizing your Python backend

For the backend component of your application written in Python, start by placing a Dockerfile at the root directory of the backend code. This will detail the necessary setup for running your Python application within a Docker container, allowing for smooth deployment across various environments. Below is a generalized and reusable Dockerfile for a Python backend.

In the root folder of your backend project, create a file named 'Dockerfile' without any extension and include the following content:

Dockerfile:

 

# Select a base image that includes Python
FROM python:3.8-slim

# Set up a working directory in the container for your application
WORKDIR /app

# Copy the backend code into the container
COPY . /app

# Install any Python dependencies listed in 'requirements.txt'
RUN pip install --no-cache-dir -r requirements.txt

# Expose the port the app runs on
EXPOSE 5000

# Set the command to run your application
# (Be sure to replace './your_app_script.py' with the relative path to the Python file that starts your application)
CMD ["python", "./your_app_script.py"]

 

Replace `./your_app_script.py` with your actual main script path. For example, if you use Flask and your main file is `app.py`, you would change the command to `CMD  ["python", "app.py"]`.

Explanation of Dockerfile directives for your Python backend:

  • `FROM python:3.8-slim`: Uses a lightweight Python 3.8 base image.
  • `WORKDIR /app`: Sets `/app` as the container's working directory.
  • `COPY . /app`: Transfers your code into the `/app` directory.
  • `RUN pip install --no-cache-dir -r requirements.txt`: Installs dependencies from `requirements.txt`.
  • `EXPOSE 5000`: Marks port 5000 for access (modify as needed).
  • `CMD ["python", "./your_app_script.py"]`: Defines the startup command (update the script name to match your entry file).

This Dockerfile template is designed to be broadly applicable to Python backends. However, remember to test it locally before deploying to ensure that your specific application runs smoothly inside the Docker container.

Step 2: Create a Docker image

Before proceeding, ensure that Docker is installed on your system. If you do not have Docker installed, you can download and install it from Docker’s official website. Once Docker is set up, you are ready to create a Docker image from your Dockerfile.

  • Navigate to the directory containing your Dockerfile in the terminal and execute the following command, substituting `<image_name>` with an appropriate name for your project:
    docker build  -t  <image_name> .

This command tells Docker to package your application into an image, which includes everything needed to run it.

Note: Make sure there's a period (.) at the end of the command - it represents the current directory and is the context Docker uses to find the Dockerfile and relevant files.

  • After building, verify that your image has been created successfully by listing all the Docker images on your system:
    docker images

You should see `<image_name>` listed in the output. If it's there, congratulations! You've just created a Docker image of your backend application and are one step closer to deployment. Remember to keep the image name relevant and easily identifiable for convenience as you manage multiple Docker images. 

Step 3: Build an artifact registry 

The next step in deploying your React app to Cloud Run is to establish a Docker repository within the Google Cloud Artifact Registry. This service allows you to manage, store, and share container images in either a private or public setting.

To begin, navigate to the Artifact Registry section in the Google Cloud Console:

  1. Open the Google Cloud Console.
  2. Find 'Artifact Registry' in the navigation menu or use the search bar to locate it.
  3. Click 'Create Repository' to set up a new repository.
  4. When prompted, assign a name to your repository that you can easily recognize and reference, such as 'react-app-repository'.
  5. Select 'Docker' as the repository format.
  6. Choose the location for your repository; this can be a specific region or a multi-region to optimize accessibility and latency.
  7. Set the access level according to the needs of your organization or for personal use. Options include 'Private' or 'Public'.
  8. Click 'Create' to finalize the creation of your new Docker repository.

Once you've created the repository, you can push your Docker images there, making them accessible to Cloud Run for deployment. Additionally, the location of your artifact registry should be considered, as deploying your Cloud Run service in the same region can speed up the deployment process and reduce latency for users.

Step 4: Push the docker images to your artifact registry

The next step is to upload your Docker images to the repository you've created in the Google Cloud Artifact Registry. This makes your images accessible for Cloud Run deployment.

  • Push your Docker image to the artifact registry by executing the following command in your terminal, replacing the placeholders with your specific details. The upload process may take up to 5 minutes to complete:

 

gcloud builds 
submit -t <hostname for docker artifact registry>/<project-name>/<repo-name>/<app-name> ./

 

Here's what each placeholder represents:

  • `<hostname for docker artifact registry>`: The hostname for your specific artifact registry (e.g., `us-central1-docker.pkg.dev`).
  • `<project-name>`: Your Google Cloud project ID.
  • `<repo-name>`: The name of the repository where your Docker images are stored.
  • `<app-name>`: The name of your application or service.

Ensure that there's a period (`./`) at the end of the command, which designates the current directory as the build context for Docker. This command triggers a Cloud Build, packages your application into a Docker image, and uploads it to the specified repository. 

Lastly, to confirm successful upload, check your images listed in the artifact registry. If your image appears there, you've successfully completed Step 4, and your image is primed for deployment on Cloud Run.

Step 5: Create a Cloud run service and deploy

To deploy your application on Cloud Run:

  1. Go to the Cloud Run section in the Google Cloud Console.
  2. Click 'Create service' to initiate a new service setup.
  3. Choose 'Deploy one revision from an existing container image' (Fig 5.1).
  4. Locate and select the Docker image you previously pushed to your artifact registry.
  5. Configure your service parameters:
    • Container port: Make sure this is the same port defined in your Dockerfile's EXPOSE instruction. For instance, if you used `EXPOSE 4200`, set the "Container port" in Cloud Run to `4200` (Fig 5.2).
    • Instance settings: You might set the minimum number of instances to `1` for constant availability and the maximum to `5` for handling peak loads without over-provisioning.
    • Maximum requests per instance: Adjust this based on expected traffic and performance requirements. A common starting point is `80`, meaning each instance will handle up to 80 concurrent requests before Cloud Run provisions another instance.
  6. Review additional settings if needed, such as environment variables, connections, or security settings.
  7. Confirm and finalize your configuration by clicking 'Create.'

This will launch a new Cloud Run service using your container image. The process may take a moment. Once complete, Cloud Run will provide a unique URL where your service is accessible.

Fig: 5.1Fig: 5.1

Fig: 5.2Fig: 5.2

Step 6: Deployment

Once the Cloud Run service successfully deploys your application, the Google Cloud Console will display a unique URL for your service. This URL is the public entry point to your application and can be used to access it from a web browser or API client.

Copy the provided URL after deployment, and save it for later use. Test the URL in a web browser to ensure that your Cloud Run service is accessible and functioning as expected. If you encounter any issues, review the service logs in Cloud Run for troubleshooting, which you can access from the Cloud Run service details page in your Google Cloud Console.

Step 7: Update the frontend file with the backend service URL

Now that your backend is deployed on Cloud Run with a URL assigned, it's time to configure your frontend to communicate with the newly-hosted service.

Locate the file in your frontend application where API calls to the backend are made. Replace the existing backend URL with the Cloud Run service URL you obtained following the backend deployment. 

Note: Remember to save your code changes to ensure the update takes effect.

This step ensures that your frontend will interact with the live version of your backend, meaning any requests made by the frontend will be processed by your application on Cloud Run.

Step 8: Deploy the frontend

After updating the backend URL in your frontend code, the next step is to deploy the frontend so it can interact with the newly-deployed backend service.

  1. Build Docker image: Construct a Docker image of your frontend application following the instructions from Step 2. Navigate to the root directory of your frontend code and run the Docker build command.
  2. Push to your artifact registry: Push the frontend Docker image to your artifact registry, adhering to the details in Step 4. Utilize the `gcloud` command-line tool to upload the image, readying it for deployment.
  3. Create Cloud Run service: In the Google Cloud Console's Cloud Run section, follow the process described in Step 5. This time, select the frontend Docker image you've pushed to your artifact registry.
  4. Configure service: Set the necessary parameters for your frontend service:
    • Ensure the container port matches with the `EXPOSE` directive in your frontend's Dockerfile.
    • Adjust environment variables and other configurations to your frontend's specifications.
  1. Deploy: After the deployment, Cloud Run will generate a URL for your frontend service. Save this URL for subsequent access to your frontend, in the same way you did with the backend service's URL.

In case the deployment does not succeed, consult the deployment logs for error messages:

  • Open the 'Logs' tab in the Cloud Run interface to troubleshoot.
  • Apply any necessary fixes based on the logs and reattempt the deployment.

Upon successful deployment, a URL will be generated. Use this URL to access and verify the functionality of your application. You have completed the deployment process for your full-stack application on Cloud Run. 

With both frontend and backend services now deployed on Cloud Run, you have functional URLs ready for user interaction and API communication.

Conclusion

Congratulations on successfully deploying your first React application on Cloud Run in just minutes!

This quick and efficient process is ideal for testing or demonstrating a Proof of Concept to clients. If this guide has been enlightening about Cloud Run's capabilities, please share it with others who might benefit.

Have questions? Drop a comment below, and thanks for being a part of this journey!