Deployment with GitHub Actions for Nuxt projects

Nov 12, 2020 · 17 min read
Deployment with GitHub Actions for Nuxt projects

We explored how to create a Git-based headless CMS with NuxtNuxt Content module, and Git submodules in my previous post. To enable auto deployment when there is new content and complete the workflow with a submodule, we need extra help from an automation tool, such as GitHub Actions.

TL;DR

You can use the tutorial as a reference to set up any project using Git submodules, without limit to Nuxt. All you need is to perform the following:

Need more details? Let's go.

GitHub Actions

GitHub Actions, developed by GitHub, are scripts containing YAML code. These scripts help automate development workflow by a series of custom tasks (actions) directly on GitHub repositories. The action codes live as part of the repository, allowing people to modify them in the local environment and commit them accordingly.

You can manually create your actions script or choose and combine different existing actions on GitHub Actions Marketplace to make one. An example of searching for Vercel Deployment Action in the Marketplace for GitHub Actions is as below:

Search for Vercel deploy Action

GitHub Actions can run on any platforms such as Linux, Ubuntu, etc., and any frameworks, including Node.js, Rust, Python, etc. Each GitHub Action provides a detailed log, which is useful for deployments' troubleshooting on run-time.

With GitHub Actions, we can afford to have testings enabled for our codebase in multiple environments simultaneously. A usual use case for GitHub Action is an E2E (end-to-end) test suite that runs whenever there is a new PR created. It ensures code coverage and lessens the burden for code reviewers.

So, how do we run GitHub action(s)?

Triggering a GitHub Action

We can trigger a GitHub Action using the following types of events:

  • Workflow
  • Scheduled
  • Webhooks
  • External

Great. How do we start working with GitHub Actions?

Well, first, we need to create a workflow .yml file.

Create your action script

To start using GitHub Actions, go to your GitHub Repository and select Actions tab.

Actions tab in repo

We can ignore the suggested templates and choose to set up a workflow ourselves as following:

Create a workflow

Once clicked, GitHub will automatically create a new .yml file located in .github/workflows/ directory with some sample code and redirect us to an online editor session to start modifying the file.

Edit workflow template

We will write the workflow code in YAML language, which is strictly sensitive and in order. Below are a few common basic rules to note for an easy start with YAML syntax:

  • A list or object's property starts with - (a dash and space)
    fruits:
    - orange
    - kiwi
    # it equals to fruits: [orange, kiwi] or fruits: {orange, kiwi}
    
  • A field with value is denoted by key: value (there must be a space between colon and value).
    name: Maya
    
  • We can span multiple lines for values using | - result will be the same as an appearance in code (with trailing spaces). And > will convert new lines to spaces, making all into one single line.
    # the following will appear the same
    description: |
     I'm a developer
     and a speaker
    
    #here it will appear as Senior Frontend in one single line
    title: >
     Senior
     Frontend
    
  • Item's nested property, or step's breakdown for a sequence need to have single space as line prefix, in respect to its nesting parent. Otherwise, it will be treated at the same level as the parent instead of nesting inside.
    steps:
     step 1:
      # nested step actions
     step 2:
      # nested step actions
    name: Deploy
    

    This means there are two steps step 1, step 2 nested inside steps, and name and steps are at the same level.
  • Use # for commenting.

Depending on what you want to achieve, you can edit the created .yaml file to add/modify steps or actions. Indeed, you can always choose an existing template for your workflow and change it per need, instead of creating from scratch 😉.

Next, let's design our workflow and start coding!

Design your workflow

We are going to set up a workflow that connects two repositories:

  • portfolio - this is the main public repository where the codebase of the website resides. It has a submodule which is
  • content - the private submodule repository that contains all the content, blog posts, etc.

Our workflow goal is straightforward: whenever there is a commit merged to our submodule repository content, it will trigger the build and deployment on our main repository - portfolio

The workflow diagram is as follows:

Workflow design for deployment

Based on the above design, we need to perform the following tasks:

  1. Once a commit is merged to the master branch of content, we need to make sure it's a valid commit and then dispatch an event to inform the portfolio repo about this change.
  2. Upon receiving this event, the repo portfolio performs a git submodule update --remote --recursive to have its submodules' refs updated. The local content version at this point reflects the latest changes.
  3. Then it needs to push a commit with the updated refs to the master branch to keep itself synchronized with content.
  4. Finally, it triggers a new build and deploys the latest version to production, using the relevant CLI command (vercel —prod for Vercel or netlify deploy for Netlify).

Great. Before we continue, there is one thing we need to do. Communicating between two repositories or more requires authentication. Thus, to ensure the workflow between two repositories happens securely, we need to setup GitHub PAT - personal access token.

Setting up the PAT and the secrets

PAT - Personal Access Token is required for authentication when using GitHub API in place of passwords. Each PAT is tied to a set of access permissions options we selected during the creation.

And dispatching "communicating" events to a repository needs a unique PAT with either:

  • write access to the target repository.
  • Or access to public_repo is enough if the target repository is public.

In our workflow, we only need to enable public_repo permission, since our target portfolio repository is a public one.

To generate a new PAT, navigate to your GitHub Settings, and select Developer Settings from the left side menu.

Developer settings in GitHub account

Select Personal Access Token on the left side menu, then hit "Generate New Token" button to create a new PAT.

Create new PAT token

Finally fill in the note to describe what the PAT is for, and tick public_repo to enable the access.

Fill in PAT info

Once done, hit the button "Generate token" at the bottom of the page. And our PAT is ready for us to copy and use.

Save and generate PAT

Great. We have to add it as an encrypted environment variable and use it for GitHub Actions in both repositories.

Adding secrets key

We can add the newly created PAT as a secret key in the following steps:

  1. Navigate to the content repository Settings tab and select Secrets on the left side menu.
  2. Create a new repository secret. Create new repository's secret key
  3. Paste the key you copied to the Value section, and give it the name DISPATCH_HOOK_TOKEN. Then hit "Add secret" to save the new key.
Add dispatch hook token secret key
  1. Repeat the same steps to portfolio repository and have DISPATCH_HOOK_TOKEN key created with the same value.

This variable is available to access workflow running environment using secrets. It is a global object given by GitHub, which contains all the encrypted environmental variables to use securely in the workflow. To access the created key, we use secrets.DISPATCH_HOOK_TOKEN.

So far, so good. Now, let's put our workflow into code, shall we?

Setting the workflow for submodule

Let’s create a workflow file main.yml followed the instruction above in the submodule repo content. Then we remove the sample code provided in main.yml and start with only the following:

Submodule workflow file

In which:

  • on section defines when the action will run.
  • jobs section contains a list of tasks to run for a workflow, in parallel or sequentially.
  • runs-on: ubuntu-latest means we want to run our workflow on Ubuntu environment.
  • steps defines the sequences of tasks within a job to be executed.
  • uses defines what GitHub Action to use for a task. Here actions/checkout@v2 is the action for checking out a repo for the workflow to access it during run time. This task is required for the workflow to perform tasks on the repo in its local environment.

Tip: Do not worry if you get lost. GitHub editor has auto hint and error highlighting to help you get started smoothly. You can also refer to YAML Syntax Documentation for better understanding.

Great. The workflow file is ready for the next modification - defining how to communicate with our target repo - portfolio from the content.

Dispatch event to communicate between repositories

For communication between repositories, GitHub API provides us a Webhook event called respository_dispatch. This event allows us to trigger a specific workflow outside of GitHub according to a custom event_type value sent in the Webhook payload. We can set the target event_type in payload when we create the dispatch event.

To enable this Webhook event, we perform the following:

  1. Use the Repository Dispatch Action and click the "Use the latest version" button.
Repository Dispatch Action
  1. Hit the Copy icon to copy the code snippet.
Install and copy code
  1. Paste in our sample code as the new step under steps
  2. Provide the configurations for the action:
    with:
    token: ${{ secrets.DISPATCH_HOOK_TOKEN }}
    repository: ${{target_repo}}
    event-type: merge-content
    client-payload: '{"message": "New blog posted!"}'
    

    In which:
    • token - the default GitHub token doesn't have the scope for dispatching respository_dispatch event. For this purpose, we use our DISPATCH_HOOK_TOKEN created previously by using secrets.DISPATCH_HOOK_TOKEN.
    • repository - the target repo to receive this event. Replace ${{target_repo}} with your target repo path in the format your-github-user/repo-name
    • event-type - what type of event you want to tell the other repo. Type should be unique as on the target repo, GitHub will trigger a specific workflow according to an event type it attaches. In our example we use merge-content
    • client-payload - anything else you would like to add, it can be a Twitter message for posting after the target repo finishes deploying.

    Our code becomes:
    # main.yml
    
    name: CI
    on:
      push:
        branches: [ master ]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          
          # Dispatch an event to the consumer repo
          - name: Repository Dispatch
            uses: peter-evans/repository-dispatch@v1.1.1
            with:
            token: ${{ secrets.DISPATCH_HOOK_TOKEN }}
            repository: ${{target_repo}}
            event-type: merge-content
            client-payload: '{"message": "New blog posted!"}'
    
  3. We only want to dispatch this event on pushed commits related to content (and not a merge or any changes related to our workflow files). For that, we define the paths-ignore , the target type of request to run the request, and replace the original section under on as below:
    # Controls when the action will run. 
    # Triggers the workflow on push or pull request
    # events but only for the master branch
    on:
    push:
      branches: [ master ]
      paths-ignore:
      - '.github/workflows/**'
    

That's it. Our workflow for content submodule is complete with the following code:

```yaml
# main.yaml

name: CI
on:
  push:
    branches: [ master ]
    paths-ignore:
      - '.github/workflows/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      # Dispatch an event to the consumer repo
      - name: Repository Dispatch
        uses: peter-evans/repository-dispatch@v1.1.1
        with:
        token: ${{ secrets.DISPATCH_HOOK_TOKEN }}
        repository: ${{target_repo}} # replace this with your repo
        event-type: merge-content
        client-payload: '{"message": "New blog posted!"}'

Next, we define the workflow to trigger for the target repository - portfolio whenever merge-content event type is dispatched from content.

Setting up the deployment workflow for the main repo

On the main repo - portfolio, let's add a new workflow file to handle the dispatched event merge-content from its submodule using the same instructions.

To make it more specific, we change the default filename from main.yml to deploy_content.yml, and change the name key to a more self-explained title

name: Deploy on new content dispatch

We need to configure when the action runs. In our case, it happens when it's a repository_dispatch event whose type is merge-content.

name: Deploy on new content dispatch
on:
  repository_dispatch:
    types: [merge-content]

For this workflow, we have the following steps:

  • Check out the submodule repo, so our job can access it for the build.
  • Update the submodule ref to reflect the new content
  • Make a commit on this updated ref and push back to the repo, so the repo is always up-to-date.
  • Build and deploy.

Let's get started with each step, shall we?

Check out the submodule repo into the main repo

This step is essential to have all the submodule content available for building in the main repo. To check out a specific repo as a submodule, we need to use GitHub Action actions/checkout@v2 with the following configurations:

  • token - the same PAT token we put to allow dispatching events on content repo - DISPATCH_HOOK_TOKEN
  • submodules - we set this flag to true to inform this cloning task is applied for submodule, not for a regular repo

And the code is:

- name: Clone/Checkout submodules
  uses: actions/checkout@v2
   with:
    token: ${{ secrets.DISPATCH_HOOK_TOKEN }}
    submodules: true

Cool. Since the repo is checked out, we can update the submodule local version's refs to reflect the new content.

Update the submodule

This step requires two separate sub-steps to happen sequentially:

  • Update the submodule's refs from the remote repo by the following step:
    - name: Update submodules content
      run: git submodule update --remote --recursive
    
  • Pull the remote repo's content according to the latest refs
    - name: Pull the content
      run: git pull
    

The submodule's content in the main repo should be up-to-date by this point.

Next, we need to update the main repo with the new submodule refs received.

Commit the new submodule's refs

For this step, we use Git Auto Commit Action, which can be found on the marketplace:

Git Autocommit Action

And add the following code:

- name: Update submodules index
  uses: stefanzweifel/git-auto-commit-action@v4

With the following configurations for the commit:

with:
 # Required
 commit_message: Update submodule ref

 # Optional commit user and author settings
 commit_user_name: My GitHub Actions Bot
 commit_user_email: actions@github.com
 commit_author: My GitHub Actions Bot <actions@github.com>

In which:

  • commit_message - Description of the commit. This field is required.
  • commit_user_name - Name of the committer.
  • commit_user_email - Email of the committer. In our example, we use the Actions Bot actions@github.com
  • commit_author - The author of the commit.

Our code will look like:

name: Deploy on new content dispatch
on:
  repository_dispatch:
    types: [merge-content]

jobs:
  mergeContent:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Clone submodules
      uses: actions/checkout@v2
      with:
        token: ${{ secrets.DISPATCH_HOOK_TOKEN }}
        submodules: true

    - name: Update submodules content
      run: git submodule update --remote --recursive  
    - name: Pull the content
      run: git pull
    - name: Update submodules index
      uses: stefanzweifel/git-auto-commit-action@v4
      with:
        # Required
        commit_message: Update submodule

        # Optional commit user and author settings
        commit_user_name: My GitHub Actions Bot
        commit_user_email: actions@github.com # to change
        commit_author: My GitHub Actions Bot <actions@github.com>

Awesome. Finally, let's deploy the project to production!

Build and deploy with Vercel

One advantage in using Vercel is that it auto-detects the build command and directory to deploy based on your main framework. Hence we do not need to specify a job step for building the code and dependencies.

Firstly, we need to link our project to Vercel. To do so, you should set up Vercel CLI on your local machine and run the following command in the project root directory.

vercel

This step is required for Vercel CLI to know which project scope you want to deploy to. Once you finish the setting up process, a .vercel directory will be added to your directory with a project.json file, containing the following:

/*project.json*/
{
  "orgId": "example_org_id",
  "projectId": "example_project_id"
}

Copy these keys and save them as VERCEL_ORG_ID and VERCEL_PROJECT_ID in your GitHub Secrets.

To deploy to Vercel, we will use Vercel Action:

Vercel Action

And insert the following code to our workflow file:

name: Deploy to Vercel
uses: amondnet/vercel-action@v19.0.1+4
with:
 vercel-token: ${{ secrets.PAT_VERCEL_TOKEN }} # Required
 vercel-args: '--prod' #Optional
 vercel-org-id: ${{ secrets.VERCEL_ORG_ID}}  #Required
 vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}} #Required

In which:

  • vercel-token - A required token to deploy to Vercel. You can generate the token in your Vercel dashboard by going to User Settings > Tokens and clicking on Create: Where to look for the token in Vercel
    And after setting the name of the token, click the Create token button to have it generated: Create Vercel token
    Once created, copy the new token by hitting the Copy icon. Copy the Vercel token
    Then create the new secret key PAT_VERCEL_TOKEN in the repository's settings with the generated token's value. Save Vercel token as secret key
  • vercel-args - Extra arguments to pass for deployment. We use --prod to indicate the deployment is for production only.
  • vercel-org-id - it is the orgId field, which can be found in the local .vercel file. We need this field to link the project to Vercel.
  • vercel-project-id - it is the projectId field, which can be found in the local .vercel file. We need this field to link the project to Vercel.

Hit the Commit button, and our workflow is ready.

Bonus: Build and deploy with Netlify CLI

If you wish to deploy using Netlify, Netlify Deploy Action is available for us to use.

Nelify Deploy Action

Insert the following code as a new job step to our deploy-content.yml file.

- name: Netlify Deploy
  uses: jsmrcaga/action-netlify-deploy@v1.2.0
    with:
      NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
      NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
      NETLIFY_DEPLOY_TO_PROD: true
      build_command: yarn generate
      build_directory: dist
      install_command: yarn

In which:

  • NETLIFY_AUTH_TOKEN - authentication token to deploy your site on Netlify. You can generate one by navigating to your Nelify's account Settings > Applications > Personal access Tokens. Get Netlify Authentication Token
    You can create a new token by clicking on "New access token". After you give the new token a meaningful name, generate and copy it. Save Netlify Authentication Token as secret key
    Finally, create a new secret key NETLIFY_AUTH_TOKEN in the repository settings with the copied token's value.
  • NETLIFY_SITE_ID - the site where you deploy your site to. You can find it through the Site Settings > General > Site details > Site information > API ID.
    <img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1605100057/articles/github/APP_ID_Netlify loading="lazy" alt="Getting Netlify Site ID" class="mx-auto mt-2"/>
    Once you copied it, create a new secret key NETLIFY_SITE_ID in the repository settings with that value.
  • NETLIFY_DEPLOY_TO_PROD - whether we want to deploy to production directly. We set it to true to enable deployment to production automatically.
  • build_command - which CLI command we use to build our project. Unlike Vercel, Netlify doesn't detect the build command automatically from your main framework, but leaving the freedom to users. We need to specify yarn generate since we want to build a Nuxt static site.
  • build_directory - where the generated code locates for Netlify to deploy. For Nuxt project, it is dist by default.
  • install_command - whether we use yarn or npm to install dependencies. In our case we use yarn.

That's it. Hit the Commit button to save the file to the repository, and our workflow is ready to go.

Run the workflow

Let's add an example.md file in content repo and commit it to the master branch.

---
title: Example
---

This is an example file for content.

More example

## Heading 2

Once the commit is pushed, we can go to the Actions tab of both repositories and select the related workflow to view the triggering status:

  • In content repo's workflow Dispatch workflow event on content repo
  • In portfolio repo's workflow Building main repo on event status

Once all the jobs are green, we complete our deployment workflow! Whenever we make new content, commit and push it to the master branch of content. The new content will then be deployed to production automatically! 🎉

Resources

Summary

In general, automation is essential. By setting up automation for complicated and time-consuming routine tasks like CI/CD (continuous integration/ continuous deployment) on complicated infrastructure, we can afford to be more productive and focus on a more significant area, such as code quality. By setting up your Nuxt project with Nuxt content and Git Submodule as Headless CMS, having a seamless automatic deployment workflow is essential. Once you have the workflows set up, you are relaxed and rest assured that you don't have to manually check and deploy whenever there is a content change from now on. Automation FTW!

Also, guess what, you just learned a new DevOps skill 😉. Isn't it awesome?

After trying both Vercel and Netlify, I personally prefer the setting up for Netlify deployment using GitHub Actions. The user experience in Netlify is more trivial and seamless, with less local setup required for the CLI tool. At the same time, you have more control over how you want to build your projects (which command to give). And Netlify provides such an out-of-the-box solution in working with submodules!

What's else? How about trying to automate your unit testing workflow with GitHub Actions and ensure you cover your codebase with tests 😄? I'm curious to see what you can create with GitHub Actions to ease your development cycle.

👉 If you'd like to catch up with me sometimes, follow me on Twitter | Facebook.

Like this post or find it useful? Hit the share button 👇🏼 or Buy me a coffee 😉