# Project 20: Deploying React Application to Google cloud run using Github Action

## Introduction:

* **Overview**:This project focuses on streamlining the deployment of a React application by leveraging the power of Google Cloud Run and GitHub Actions. The goal is to create a robust and automated Continuous Integration/Continuous Deployment (CI/CD) pipeline that efficiently builds, tests, and deploys our application whenever changes are pushed to the GitHub repository.
    
* **Why this Technology:**
    
    * React : A popular JavaScript library for building dynamic and interactive user interfaces, offering component-based architecture and a thriving ecosystem.
        
    * **Google Cloud Run:** A serverless platform that automatically scales our application based on demand. **<mark>This eliminates server management overhead and optimizes costs.</mark>**
        
    * **GitHub Actions:** A CI/CD platform integrated directly into GitHub, enabling automated workflows triggered by code changes. This simplifies the process of building, testing, and deploying our application.
        

## **Architecture:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735106222207/698c0e74-f65c-4faa-9c74-7b3dada56947.png align="center")

We'll be using this GitHub repository as our foundation: [https://github.com/patelajay745/Second-brain.git#development](https://github.com/patelajay745/Second-brain.git#development)

### Project:

* Clone git repo to local:
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735179447070/2c20b4e9-6896-4088-951f-d2ba7a2dce8d.png align="center")
    
* First we will setup everything for backend followed by frontend. Now we will add docker file to backend folder. I have create a multistage docker file.
    
* Add Dockerfile to backend folder:
    
    ```yaml
    FROM node:18-slim AS builder
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --frozen-lockfile
    COPY . .
    RUN yarn build
    
    # second stage
    FROM node:18-slim
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production --frozen-lockfile
    COPY --from=builder /app/dist ./dist
    EXPOSE 8080
    CMD ["yarn", "start"]
    ```
    
* **Now we need to setup Google cloud.**
    
      
    1\. Create new Project from google cloud console. Click on “New Project”
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735180285165/ea90c2b7-97a4-4830-aca4-18dc2aa1493e.png align="center")
    
    2. Provide name for the project and click on “Create”.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735180342744/5f7ad519-3e42-40ea-b135-2bb860ffc79b.png align="center")
        
    3. Goto IAM and click on “Service Accounts”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735186997333/40cafb8e-7a29-46e9-a9e2-5141b255e200.png align="center")
        
    4. Click on “Create Service Account”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735186981373/63e63095-9b35-4b42-8641-b7a554c090d0.png align="center")
        
    5. Provide name to service account “brain-app-sa”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187068822/6d5144b5-347d-447c-af31-f6c1e1e15296.png align="center")
        
    6. Add three roles to this service account “Artifact Registry adminnistrator”, “Cloud Run admin”, “Service Account User”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735191387043/f4b36083-2484-4d0a-bc5b-fef4ab5ef547.png align="center")
        
          
        
    7. Click on “continue” then “Create”.
        
    8. Click on name of the service account.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187286716/0b9c6c2f-2612-4a2e-9fad-59ead9ce973f.png align="center")
        
    9. Click on “Keys”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187339561/a0cb29bf-7bde-4c2d-8458-35d56f8b257d.png align="center")
        
    10. Click on “Add key”. Then Click on “Create new Key”.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187396747/90f0ec7b-68d1-4bc5-a11d-c01ade107427.png align="center")
        
    11. Choose Json and then click on “Create”. It will download new key to access google cloud using created service account.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187418306/d0e4ae63-a60e-4b6e-8072-f3818c062327.png align="center")
        
    12. Go to “Artifact Registry”. Enabled API for that if required. Click on “Create Repository”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187833611/7433d946-ea98-414d-9aee-e0d621aaee39.png align="center")
        
    13. Provide name and region then click on “Create”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735187979391/80fd5fb3-71c6-40f5-825e-5dc9cf376afc.png align="center")
        
          
        **Now we need to setup required secret and variable to github repo.**
        
          
        Go to Settings on your Github Repo.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735188264102/faff4ff5-274a-4161-9afc-6c0eebce2a39.png align="center")
        
        Click on “Secrets and variables” then click on “Actions”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735188346197/ce85fe85-cb64-4567-979e-24cc82048637.png align="center")
        
        We need to create 3 secrets (You can either repository secrets or Environment secrets)
        
          
        1\. ACCESS\_TOKEN\_SECRET (Randon string for json Webtoken)  
        2\. GOOGLE\_CREDENTIALS (Json data of service account key)  
        3\. MONGODB\_URI (managed mongo db access url)
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735189202505/ca6f7c50-b3f7-43f8-80f5-4252c9c64342.png align="center")
        
        Now click on “Variables” and then click on “Manage environment secrets”
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735189366766/d8e9b9a5-91e4-4479-ba6c-53860f882dc4.png align="center")
        
        Then Add 2 new variables.  
          
        1\. GCP\_PROJECT\_ID (Google cloud project Id)  
        2\. GCR\_REPO\_NAME (Google artifact registry name)
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735189696347/36e8d5e2-51cb-414b-8b49-d095efc31c6a.png align="center")
        
        Now Go to Cloned local repo. Add Folder “.github”. Inside that folder create folder “workflows” then create file named “backend.yml”  
          
        .github&gt;workflows&gt;backend.yml
        
        ```yaml
        name: Backend Cloud Run Deployment
        
        on:
          push:
            branches:
              - development
            paths:
              - "backend/**"
          pull_request:
            paths:
              - "backend/**"
        
        jobs:
          build-and-push:
            runs-on: ubuntu-latest
            environment: dev
            steps:
              - name: checkout
                uses: actions/checkout@v4
        
              - name: Google Cloud Auth
                uses: google-github-actions/auth@v2
                with:
                  credentials_json: "${{ secrets.GOOGLE_CREDENTIALS }}"
                  project_id: ${{ vars.GCP_PROJECT_ID }}
        
              - name: Setup Cloud SDK
                uses: "google-github-actions/setup-gcloud@v2"
        
              - name: Configure Docker
                run: gcloud auth configure-docker us-central1-docker.pkg.dev
        
              - name: Build
                env:
                  IMAGE_NAME: backend
                  PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
                run: |-
                  docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest -f backend/Dockerfile backend/.
                  docker push us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest
        
              - name: Deploy to Cloud Run
                run: |
                  gcloud run deploy second-brain-backend \
                      --image us-central1-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.GCR_REPO_NAME }}/backend:latest \
                      --platform managed \
                      --port=8080 \
                      --region us-central1 \
                      --allow-unauthenticated \
                      --set-env-vars "MONGODB_URI=${{ secrets.MONGODB_URI }},ACCESS_TOKEN_SECRET=${{secrets.ACCESS_TOKEN_SECRET}},ACCESS_TOKEN_EXPIRY=1d"
        ```
        
        make required changes to above pipeline.  
          
        Now let’s push code to remote repo and check pipeline has any error.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735190417716/499c17bf-69c7-45a0-9c1c-cf7080250223.png align="center")
        
        We got error saying our cloud run api is not enabled. we need to enable and then try again.
        
          
        Finally Pipeline got executed successfully and we got url for backend.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735191798154/f27ad498-1542-4f84-a396-e30246a79987.png align="center")
        
        Try url to test your backend in postman. Keep this backend url handy , we will need to add this to frontend to solve cors problem.
        
          
        **Now, Moving to frontend part.**  
          
        We need to add one more secrets to github repo.  
          
        VITE\_BACKEND\_URL (backend url ,which we got in above step)
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735193488831/716a8139-4386-4b2b-bf44-e3b8e3340474.png align="center")
        
        Add Docker file to Frontend Folder
        
        ```dockerfile
        FROM node:20-alpine AS builder
        RUN apk add --no-cache yarn
        WORKDIR /app
        COPY package.json yarn.lock ./
        RUN yarn install --frozen-lockfile --cache-folder ./.yarn-cache
        COPY . . 
        ARG VITE_BACKEND_BASE_URL
        RUN yarn build
        
        FROM nginx:alpine
        COPY nginx.conf /etc/nginx/conf.d/default.conf
        COPY --from=builder /app/dist /usr/share/nginx/html
        
        EXPOSE 80
        CMD ["nginx", "-g", "daemon off;"]
        ```
        
        Add frontend.yml to .github/workflows folder
        
        ```yaml
        name: Frontend Cloud Run Deployment
        
        on:
          push:
            branches:
              - development
            paths:
              - "frontend/**"
          pull_request:
            paths:
              - "frontend/**"
        
        jobs:
          build-and-deploy:
            runs-on: ubuntu-latest
            environment: dev
            steps:
              - name: checkout
                uses: actions/checkout@v4
        
              - name: Google Cloud Auth
                uses: "google-github-actions/auth@v2"
                with:
                  credentials_json: "${{ secrets.GOOGLE_CREDENTIALS }}"
                  project_id: ${{ vars.GCP_PROJECT_ID }}
        
              - name: Set up Cloud SDK
                uses: "google-github-actions/setup-gcloud@v2"
        
              - name: configure Docker
                run: gcloud auth configure-docker us-central1-docker.pkg.dev
        
              - name: Build
                env:
                  IMAGE_NAME: frontend
                  PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
                run: |-
                  docker build --build-arg VITE_BACKEND_BASE_URL=${{ secrets.VITE_BACKEND_URL }} -t us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest -f frontend/Dockerfile frontend/.
                  docker push us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest
        
              - name: Deploy to cloud run
                run: |
                  gcloud run deploy second-brain-fe \
                    --image us-central1-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.GCR_REPO_NAME }}/frontend:latest \
                    --platform managed \
                    --port=80 \
                    --region us-central1 \
                    --allow-unauthenticated \
                    --set-env-vars "VITE_BACKEND_BASE_URL=${{secrets.VITE_BACKEND_URL}}"
        ```
        
        Now let’s push code to run frontend pipeline. It got ran successfully and we got our frontend url but wait application will still not run.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735193737918/8ddb2b8f-644c-4d69-9e7c-1af26e068756.png align="center")
        
        as final step we need to add frontend url to backend end so our origin can identify for cors.
        
          
        set new env to backend cloud run CORS\_ORIGIN=”your frontend url” and your backend.yml will look like this.
        
        ```yaml
        name: Backend Cloud Run Deployment
        
        on:
          push:
            branches:
              - development
            paths:
              - "backend/**"
          pull_request:
            paths:
              - "backend/**"
        
        jobs:
          build-and-push:
            runs-on: ubuntu-latest
            environment: dev
            steps:
              - name: checkout
                uses: actions/checkout@v4
        
              - name: Google Cloud Auth
                uses: google-github-actions/auth@v2
                with:
                  credentials_json: "${{ secrets.GOOGLE_CREDENTIALS }}"
                  project_id: ${{ vars.GCP_PROJECT_ID }}
        
              - name: Setup Cloud SDK
                uses: "google-github-actions/setup-gcloud@v2"
        
              - name: Configure Docker
                run: gcloud auth configure-docker us-central1-docker.pkg.dev
        
              - name: Build
                env:
                  IMAGE_NAME: backend
                  PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
                run: |-
                  docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest -f backend/Dockerfile backend/.
                  docker push us-central1-docker.pkg.dev/$PROJECT_ID/${{ vars.GCR_REPO_NAME }}/$IMAGE_NAME:latest
        
              - name: Deploy to Cloud Run
                run: |
                  gcloud run deploy second-brain-backend \
                      --image us-central1-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ vars.GCR_REPO_NAME }}/backend:latest \
                      --platform managed \
                      --port=8080 \
                      --region us-central1 \
                      --allow-unauthenticated \
                      --set-env-vars "MONGODB_URI=${{ secrets.MONGODB_URI }},ACCESS_TOKEN_SECRET=${{secrets.ACCESS_TOKEN_SECRET}},ACCESS_TOKEN_EXPIRY=1d,CORS_ORIGIN=https://second-brain-fe-475439396032.us-central1.run.app"
        ```
        
        Now run backend pipeline and test your app.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735194272960/11468e44-87b9-41e2-b806-ac3c384fe3f7.png align="center")
        
        You can also monitor using google cloud monitoring and can set alert for uptime.
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1735194356415/ee1a5d34-b713-4813-b29c-2b7657232ea2.png align="center")
        
        If you have reached till here then Can you change your cloud run url to custom domain? If you need help then let me know, I will help out.  
          
        Conclusion : This project showcased the power and efficiency of deploying a React application using Google Cloud Run and GitHub Actions. By automating our CI/CD pipeline, we've achieved a streamlined workflow for building, testing, and deploying our application with minimal manual intervention. This setup ensures our application is always up-to-date and readily available to users.
        
          
        While we've covered the essential steps, there's always room for improvement! Consider exploring advanced techniques like blue-green deployments for even smoother and risk-reduced releases. Happy coding!
