Deploy a React App on GCP with Google Cloud Run 
Tags: react, googlecloud, serverless, docker
Published: 2022-01-16 16:03:46

So you have created your first React project and now you are ready to try to deploy it? Fear not, Google offers Cloud Run, a very simple but powerful tool that helps you do just that. I will show you in three simple steps, how you can deploy a containerized React app with Cloud Run.

_This tutorial assumes you have already setup your Google Cloud Project and have your React app running locally._

## Can I simply upload my code into a bucket?
When I tried to deploy my first React project, I was already experienced with Cloud Run for various other projects. When I wanted to deploy the app to GCP, my first idea was to run a simple `npm run build...` and upload the compiled output folder into a Google Cloud Storage (GCS) bucket.

After I was done, I realized that this approach would not work. GCS is trying to serve all routes from a path inside the bucket. So if you create a page `/login` in React, GCS will try to serve a file located inside the subfolder of the GCS bucket. This will fail, because no such file exists. React is supposed to handle the routing client side. More info on this can be found [here](https://www.ackee.agency/blog/how-to-host-static-react-apps-in-google-storage-bucket-behind-cloudflare-cdn).

The "easiest" way to achieve a working routing is to use Google App Engine. However, I find App Engine not very scalable for multiple reasons. The major issue I faced is that the location of your App Engine can not be changed once it was activated for a project (Why?), and you can only have one App Engine location for the whole project.

## Cloud Run
The better solution to go with is Google Cloud Run. Cloud Run is actually based on Knative, [a "Kubernetes-based platform to deploy and manage modern serverless workloads"](https://knative.dev/docs/). The main benefit of Knative is that it makes scaling of any stateless applications very easy. You simply provide a docker image and Knative will scale it up to as many instances as needed.

In comparison to directly running Knative your own Kubernetes cluster, Cloud Run is easier to set up and maintain. It is also very cheap for projects, where you expect a small load of traffic, because Cloud Run is billed per usage (e.g. per request to the service). Another advantage of Cloud Run is the ability to revert your deployments within less than 10s. This feature saved me some headaches in the startup that I worked with.

### 1. Create a docker image that contains your compiled React app
You need to create a file `Dockerfile` in the root directory of your project. We will use a multi-stage docker file in this step so be sure to copy all of the following code **into a single file**.

```docker
FROM node:lts-alpine as builder

# by only copying package.json, before running npm install. We can leverage dockers caching strategy for steps. Otherwise docker needs to run npm install every time you change any of the code.
COPY package.json ./
RUN npm install
RUN mkdir /app-ui
RUN mv ./node_modules ./app-ui
WORKDIR /app-ui
COPY . .
# in this step the static React files are created. For more info see package.json
RUN npm run build

```
After running the builder we have all our static files available. However we still need a way of serving them to the client. We use nginx for this.
```docker
FROM nginx:alpine

# copy the .conf template
COPY ./.nginx/nginx.conf /etc/nginx/nginx.conf

## Remove default nginx index page and replace it with the static files we created in the first step
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /app-ui/build /usr/share/nginx/html
EXPOSE 80

CMD nginx -g 'daemon off;'
```

In the first stage of the docker file (the "build" stage), we call the "build" script. This needs to be defined in your `package.json`. It triggers the compilation of your react code.
```json
{
  ...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    ...
  }
  ...
}
```
In the second stage of the docker file, we copy the configuration file of nginx into the server. So please create a file `.nginx/nginx.conf` with the following content.
```nginx
worker_processes 4;

events { worker_connections 1024; }

http {
    server {
        listen 80;

        gzip on;
        gzip_disable "msie6";

        gzip_comp_level 6;
        gzip_min_length 1100;
        gzip_buffers 16 8k;
        gzip_proxied any;
        gzip_types
            text/plain
            text/css
            text/js
            text/xml
            text/javascript
            application/javascript
            application/json
            application/xml
            application/rss+xml
            image/svg+xml;

        root   /usr/share/nginx/html;
        index  /index.html;
        include /etc/nginx/mime.types;

        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}
```
In the configuration the line `try_files $uri $uri/ /index.html;` tells the server to try to locate the requested file in the `/usr/share/nginx/html` directory. If it is not found we serve the `index.html` file.

### 2. Upload the Docker Image to Google Container Registry
In the terminal navigate to your projects root folder and run ```bash
gcloud builds submit --tag gcr.io/<your-project-id>/react-with-cloudrun
```
This will build the docker image using the Dockerfile that you have created in the previous step and upload it to the Container Registry.
### 3. Create the Cloud Run service
![Navigating to Cloud Run in the GCP console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3wj68130o21xbugxxve1.png)
In the Google Cloud Console, navigate to the Cloud Run overview. There, create a new service. During the creation of the service select the image you uploaded in the previous step. Choose the port 80 because this is where our NGINX server is listening.
![Creating a new Cloud Run Service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5huhqbqut3fz4g3f6b5.png)

### 4. (Optional) Map your custom domain to the service
If you own a domain and want to make your React app available with that domain you can set it up with Cloud Run.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu800q88mqnn1cu1z07w.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vt7lb4dorelnycihbaox.png)
## Where to go from here
You have successfully deployed your React app with Cloud Run! 

As a next step you can try to set up an automated CI/CD pipeline with Google Cloud Build. It fits perfectly with Cloud Run and Docker images.

Instead of using the web GUI to create and manage the resources of Google Cloud Platform, you can also start using Terraform.

When you have multiple services running in Cloud Run, [Google Cloud Load Balancing](https://cloud.google.com/load-balancing) offers an efficient way of routing requests.