petitviolet blog

    How to deploy a Next.js application running on Cloud Run via GitHub Actions

    2022-01-25

    GCPNext.jsCloud RunGitHub Actions

    As I posted before, this blog is running on GCP Cloud Run and also is built with Next.js.

    Running a blog powered by Next.js on Cloud Run Build a blog with Next.js and run it on GCP Cloud Run which is a managed platform for enabling users to run containers very easily This blog had been built with [Gatsby](https://nextjs.org), but I decided to migrate to [Next.js](https://nextjs.org) just for learning it. This post is going to describe what the arcitecture is and how to deploy this blog to [GCP Cloud Run](https://cloud.google.com/run) which is a serverless platf

    Previously, before migrating to Next.js from Gatsby, this blog used to get deployed automatically via GitHub Actions every time PR is merged. In case you're interested, see this post for details.

    Manage new entry using GitHub Actions Deploy preview version and notify it [The previous entry](/post/deploy-blog-via-github-actions) describes how to deploy static sites through GitHub Actions. As the next step, this entry is going to show how to manage new entries, and also how to publish entries based on schedules. Deploy preview versions triggered by pull requests
    blog.petitviolet.net
    2020-03-18 [GoogleAppEngine, GitHub Actions]

    GitHub Actions Dependencies

    In terms of interacting with GCP, it depends on the following actions:

    • google-github-actions/setup-gcloud for preparation to get connected with GCP
      • README describes they have multiple ways for authorization, but using credentials_json might be the easiest way, though it'd be less secure than using workload_identity_provider.
    • google-github-actions/deploy-cloudrun is a handy workflow to interact with Cloud Run specifically
      • As it is dedicated to deploy Cloud Run application, it supports almost all of options Cloud Run provides.

    YAML

    The following YAML configuration is to:

    • get authorization with GCP
    • build a docker image that contains my application, i.e. this blog
    • deploy the image on Cloud Run
    • delete an older revision in Cloud Run to clean up
      • not going to describe detail, but every PR gets deployed to Cloud Run as a preview version
    .github/workflows/main.yaml
    name: main
    
    on:
      push:
        branches:
          - master
        paths:
          - '**.js'
          - '**.ts'
          - '**.tsx'
          - 'src/**'
          - 'content/**'
          - '**.yaml'
    
    env:
      PROJECT_ID: ${{ secrets.GCP_PROJECT }}
      SERVICE: blog
      REGION: asia-northeast1
      URL_MAP: ${{ secrets.GCP_URL_MAP }}
    
    jobs:
      run:
        runs-on: ubuntu-latest
    
        permissions:
          contents: 'read'
          id-token: 'write'
    
        steps:
        - uses: actions/checkout@v2
    
        - name: setup node
          uses: actions/setup-node@v2
          with:
            node-version: '17.x'
            cache: 'yarn'
    
        - name: authenticate with GCP
          id: auth
          uses: google-github-actions/auth@v0.4.2
          with:
            credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
    
        - name: Set up Cloud SDK
          uses: google-github-actions/setup-gcloud@v0.3.0
          with:
            project_id: ${{ env.PROJECT_ID }}
    
        - name: Docker login on GCR
          run: |
            gcloud auth configure-docker --quiet
    
        - name: Set revision
          id: revision
          env:
            TZ: 'Asia/Tokyo'
          run: |
            echo "::set-output name=REVISION::$(date '+%Y%m%d')-${GITHUB_SHA::6}"
    
        - name: Set docker image
          id: image
          run: echo "::set-output name=IMAGE::asia.gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ steps.revision.outputs.REVISION }}"
    
        - name: build docker image
          run: | # yarn and yarn build will be running while building docker image
            docker build -t ${{ steps.image.outputs.IMAGE }} . 
    
        - name: docker push
          run: |
            docker push ${{ steps.image.outputs.IMAGE }}
    
        - name: Deploy to Cloud Run
          uses: google-github-actions/deploy-cloudrun@v0.6.0
          with:
            service: blog
            image: ${{ steps.image.outputs.IMAGE }}
            region: asia-northeast1
            no_traffic: false
            tag: "v${{ steps.revision.outputs.REVISION }}" # need to start with an alphabetical letter
    
        - name: Updates traffic
          uses: google-github-actions/deploy-cloudrun@v0.6.0
          with:
            service: blog
            tag_traffic: "v${{ steps.revision.outputs.REVISION }}=100"
            region: asia-northeast1
    
      cleanup:
        needs: run
        runs-on: ubuntu-latest
    
        permissions:
          contents: 'read'
          id-token: 'write'
    
        steps:
        - uses: actions/checkout@v2
    
        - name: authenticate with GCP
          id: auth
          uses: google-github-actions/auth@v0.4.2
          with:
            credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
    
        - name: Set up Cloud SDK
          uses: google-github-actions/setup-gcloud@v0.3.0
          with:
            project_id: ${{ env.PROJECT_ID }}
    
        - name: delete last merged PR's preview revision
          run: |
            pr_number="$(git log --grep="Merge pull request #[0-9]\+" --pretty=oneline -1 | grep -E -o "#[0-9]+" | tr -d "#" | tr -d '\n')"
            if [ -n "${pr_number}" ]; then
              echo "Merged PR: #${pr_number}"
              # disable the revision
              gcloud run services update-traffic ${{ env.SERVICE }} --remove-tags pr-${pr_number} --region ${{ env.REGION }} --quiet
              # delete the revision
              gcloud run revisions delete "${{ env.SERVICE }}-pr-${pr_number}" --region ${{ env.REGION }} --quiet
            fi
    

    That's it.

    As you might know, GCP offers Cloud Build that enables building application images in automated fashion.
    See the document: Deploying to Cloud Run  |  Cloud Build Documentation  |  Google Cloud

    In addition to deploying to Cloud Run, I wanted to control traffic and manage preview versions as the above snippet shows.