Edmund Ekott Frontend engineer who specializes in building complex UIs with JavaScript and CSS.

GitHub Actions: How to autodeploy your app

8 min read 2511

A Dropdown Menu Representing the GitHub Actions Needed to Autodeploy an App

If you’ve ever worked on an app where you’re constantly publishing changes and fixing errors, then you know how painful it is to wait for preview builds and tests to run before you merge a pull request to your production branch.

In this tutorial, we’ll show you how to automate that process with GitHub Actions. We’ll build a sample Vue.js app, write some tests, and then push to our remote repository. We’ll trigger an action to run our tests and any other checks, then merge to the production branch automatically when all checks pass. This will trigger a build to production Netlify.

We’ll cover the following in detail:

What are GitHub Actions?

GitHub Actions are instructions you create in your repository to automate certain processes in your project. GitHub Actions enable you to build, test, and deploy your code directly from GitHub.

Released in November 2019, GitHub Actions bills itself as “an API for cause and effect on GitHub.” It enables you to automate workflows based on specified events — such as push, new release, issue creation, etc. — and places those workflows in a repository so you can share, reuse, and fork your software development practices.

What is Netlify?

Netlify is a static deployment platform for automating modern web projects. Its features include continuous deployment, serverless form handling, AWS Lambda support, and more.

Netlify makes it easy to ship your web applications in three quick steps:

  1. Connect your repository
  2. Configure your build settings
  3. Deploy your site

Building a Vue.js app

First, we’ll scaffold the app using the Vue CLI.

We won’t focus too much on how Vue.js works or how to test Vue apps. For a deeper dive, check out our tutorial on testing Vue components with Vue Testing Library.

Install the Vue CLI using this command:

We made a custom demo for .
No really. Click here to check it out.

npm install -g @vue/cli # for NPM
yarn add global @vue/cli # for yarn

Create a new project using the Vue CLI by running vue create auto-deploy and selecting the options below:

The Options to Create a New Project

Open the project folder in your code editor and delete the /src/components/HelloWorld.vue component. For this demo, we’ll use the /src/App.vue component to build a simple to-do app and write tests for it.

In the App.vue file, replace the content of the file with the code snippet below:

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <h1>Todo app</h1>
    <div id="add-todos">
      <input v-model="task" type="text" class="task-input" />
      <button class="add-task-button" @click="addTask">Add Task</button>
    </div>
    <div id="todos">
      <ul>
        <li v-for="(todo, index) in todos" :key="index" class="todo-item">
          <span>{{ todo }}</span>
          <button class="delete-task-button" @click="deleteTask(todo)">Delete</button>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      task: "",
      todos: []
    };
  },
  methods: {
    addTask() {
      if (this.task !== "") {
        this.todos.push(this.task);
        this.task = "";
      }
    },
    deleteTask(todoItem) {
      this.todos = this.todos.filter(item => item !== todoItem);
    }
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  padding: 20px;
  max-width: 500px;
  margin: auto;
}
.task-input {
  margin-right: 10px;
}
.todo-item {
  display: flex;
  justify-content: space-between;
  padding: 10px 20px;
  border: 1px solid #b9b9b9;
  margin-top: 4px;
}
</style>

The snippet above is for a basic to-do app from the initial content in the component. When you visit localhost:8080 in your browser, your app should look like this:

Todo App Screenshot

Now you can add to-do items and delete them.

Setting up unit tests in Vue.js

Since we selected the unit tests option when creating the project, the Vue CLI automatically set up the configurations for us and installed a package called vue-test-utils. This crate provides utility functions similar to those of the APIs in Vue. We can use these utility functions to interact with the components.

If you look at the package.json file in the root folder, you should see this command under the scripts key:

"test:unit": "vue-cli-service test:unit"

This is the command for running our unit tests. If you try running the command at this point, you’ll see a prewritten test run in your terminal. This is to ensure that everything is working properly and our test config is correct.

Now, we’re going to delete the /tests/ folder and create a new one in the project’s root directory called __tests__. Jest checks for test specifications in two folders: /tests/unit and __tests__. It’s all a matter of preference. For this tutorial, we’ll use the __tests__ folder.

Create an App.spec.js file in the newly created folder and add the code below:

import { mount } from "@vue/test-utils";
import App from "@/App.vue";
describe("App.vue", () => {
  it("should add a task when button is clicked", async () => {
    const wrapper = mount(App);
    const task = "Do laundry";
    wrapper.find("input.task-input").setValue(task);
    await wrapper.find("button.add-task-button").trigger("click");
    expect(wrapper.vm.$data.todos.indexOf(task)).toBeGreaterThan(-1);
  });
  it("should render the tasks", () => {
    const todos = ["pick up groceries", "buy guitar pick"];
    const wrapper = mount(App, {
      data() {
        return {
          todos
        };
      }
    });
    expect(wrapper.find("div#todos>ul").text()).toContain(todos[0]);
  });
  it("should delete a task when the delete button is clicked", async () => {
    const todos = ["Order pizza for dinner"];
    const wrapper = mount(App, {
      data() {
        return {
          todos
        };
      }
    });
    await wrapper.find("button.delete-task-button").trigger("click");
    expect(wrapper.vm.$data.todos.indexOf(todos[0])).toEqual(-1);
  });
});

The code above is a test suite to ensure our component behaves exactly as we want it to. We validated that adding new tasks and deleting tasks works and that the tasks actually rendered on the page.

Now that we have the app up and running with tests included, let’s deploy our new app on Netlify so we can set up deployment from GitHub Actions.

Be sure to create a new repository for your app on GitHub and push the code. We’ll be using GitHub for the rest for this guide.

Deploying to Netlify with GitHub Actions

Head the official Netlify website to create an account, then sign in.

On your dashboard, click the New site from Git button and connect your GitHub account to Netlify.

Select the repository for your new app and use the same configurations from the image below:

Configurations for New App

Click the Deploy site button and wait for your app to be ready.

You can open your app using the link automatically generated by Netlify.

Prepping for deployment with Github Actions

Netlify provides an autodeploy feature out of the box, but we don’t want to use that; we want GitHub Actions to handle the deployment on our behalf after running tests. In some cases, you may not want to deploy the changes at that instant — for example, if a feature is still in development and not ready to be rolled out.

We want to stop Netlify from automatically building and deploying our app whenever a push is made because Netlify deploys whenever a build is successful. We also want to factor in our tests, so we’ll use GitHub Actions to do that.

Disabling builds on Netlify

The first step is to stop builds on Netlify. To do that, go to Your site’s settings > Build & deploy > Continuous Deployment. Click the Edit settings button under the Build settings section and stop builds:

Screenshot Showing Stop Builds Checkbox

Configuring Netlify secrets on GitHub

Now that we’ve disabled builds on Netlify, we need to set some secret keys provided to us by Netlify. These keys will allow us deploy our site outside Netlify.

The two main keys we need are a personal access token and the API ID of our newly created site.

The API ID of the site is located under Your site’s settings > General > Site details:

API ID View

You’ll want to take note of that because we’re going to be copying and pasting to the app’s repository on GitHub. Don’t worry, its safe — you’ll see.

Next, we’ll generate a personal access token. Follow the steps below.

Go to your account’s settings.

Account Settings View

Under Applications > Personal access tokens, create a new access token and give it a descriptive name. Be sure to copy the token before leaving the page.

How to Create New Access Token

The tokens are only displayed once. If you forgot to copy them, you’ll have to create new ones.

Open your app’s repository, navigate to Settings > Secrets, and create two secrets. The first, NETLIFY_AUTH_TOKEN is for the personal access token you just copied. The second, NETLIFY_SITE_ID, is for your site’s API ID, which is located under your site’s information.

View of Netlify Auth Token and Site ID

Your secrets should look like the screenshot above. You’ll also notice that the values for these secrets are never exposed and access to them is limited.

It’s time to set up GitHub Actions to build, test and deploy our Vue.js app for us.

How to use GitHub Actions

To show how to use GitHub actions, we’re going to create two workflows. The first will run on pull requests to the master branch and build, test, and merge the code to master. The second will run on commits to the master branch to build, test, and deploy our app this time.

I separated the workflows because there may be situations where you want to push a commit straight to the master branch from your local machine — hot fixes and whatnot. We want to make sure we don’t break anything no mater what we do.

Creating the automatic deployment workflow

In your code editor, create a directory structure — like this: .github/workflows/ — in the your project’s root folder and add a file named autodeploy.yml in the /workflows folder.

Copy the snippet below and paste in the autodeploy.yml file:

name: Auto Deploy
on:
  push:
    branches: [master] # run on pushes to master
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/[email protected] # setup the repository in the runner
      - name: Setup Node.js # setup Node.js in the runner
        uses: actions/[email protected]
        with:
          node-version: '12'
      - uses: actions/[email protected]
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: ${{ runner.os }}-node-
      - run: npm ci # install dependencies
      - run: npm run build --if-present # build the project
      - run: npm run test:unit # run the tests
      # deploy site to netlify using secrets created on repository
      - uses: netlify/actions/[email protected] 
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        with:
          args: deploy --dir=dist --prod
          secrets: '["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"]'

In the workflow file above, we created a job build-and-test with a set of sequential steps to be followed:

  1. Set up our repository in a runner
  2. Set up Node.js v12 in the runner
  3. Check whether there are any valid cached dependencies (to reduce execution time)
  4. Install any new dependencies
  5. Try building the app
  6. Try running tests
  7. If the previous two steps were successful, deploy the app to Netlify using the secret tokens we generated and stored on the repository

Commit and push the changes to GitHub, then navigate to the Actions tab on the repository. You should see the workflow running. Once it’s done, you can check your site’s dashboard on Netlify to determine whether the deploy was published successfully.

Creating the automatic merge workflow

Building this workflow is not as straightforward as the previous one because we have to consider when to automatically merge requests and when not to.

Before we get into creating the workflow file, we need to create one more secret on our repository to grant the action permission to make requests on our behalf. For this, we need a personal access token on GitHub.

Click this link to go straight to the access tokens page on your GitHub account, click the “Generate new token” button, and give it a descriptive note, such as “automerge.”

In the Scopes section, select only the repo and workflow scopes.

The Repo Checkbox

The Workflow Checkbox

Click the Generate token button and copy the token before leaving the page.

Add a new secret on your app’s repository named PERSONAL_TOKEN and return to your code editor.

In the /workflows folder, create a file named automerge.yml and paste the content of the snippet below:

name: Auto Merge
on:
  pull_request:
    branches: [master] # run on pull requests to master branch
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/[email protected] # setup repository in runner
      - name: Setup Node.js # setup Node.js un runner
        uses: actions/[email protected]
        with:
          node-version: '12'
      - uses: actions/[email protected]
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: ${{ runner.os }}-node-
      - run: npm ci # install dependencies
      - run: npm run build --if-present # build project
      - run: npm run test:unit # run tests
  mergepal-merge: # merge when build and testing is successful
    runs-on: ubuntu-latest
    needs:
      - build-and-test
    steps:
      - uses: actions/[email protected]
      - uses: maxkomarychev/[email protected]
        with:
          token: ${{secrets.PERSONAL_TOKEN}}

Now that we successfully created the workflow to automatically merge pull requests, we need to set some conditions in place so everything works as intended.

Create a file named .mergepal.yml in your app’s root folder and paste the the code from the snippet below:

whitelist:
  - good-to-go
blacklist:
  - wip
method: merge #available options "merge" | "squash" | "rebase"

The file we just created is a configuration file for mergepal, a reusable action we added to our workflow. The configurations determines when and how the pull requests should be merged.

  • whitelist can be used to list a number of labels on GitHub that will cause our pull requests to automatically merge
  • Labels added under the blacklist option will prevent the pull requests from being merged automatically
  • method enables you to specify the merge method for pull requests just like you have on GitHub

Pushing your repository to GitHub

There are just two more things to do: create new labels on GitHub and push the local repository to GitHub.

Go to the Pull requests tab on your repository and create a new table, like these:

Pull Request Tab to Create New Table

The last step is to go back to your code editor and push the project to the GitHub repository. The autodeploy workflow should have been triggered.

To test the automerge, create a new branch with changes locally and push the branch to GitHub. Create a pull request and test set one of the newly created labels before submitting.

Here’s a screenshot of the workflow in action:

A Workflow Example

You can access the repository on GitHub.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Edmund Ekott Frontend engineer who specializes in building complex UIs with JavaScript and CSS.

Leave a Reply