Set up Automated Deployments From Github With Webhook

maximization

Maxim Orlov

Posted on October 14, 2020

Set up Automated Deployments From Github With Webhook

This article was originally published a day earlier at https://maximorlov.com/automated-deployments-from-github-with-webhook/

Hosting your app on a VPS instead of a Platform-as-a-Service has you missing out on cool features such as automated deployments.

Wouldn't it be awesome if you had Heroku-like auto-deployments on your server? 💫

But how do they even work? How does one go about setting up automated deployments for an application?

Being able to push your changes and see them live a few seconds later would be a major productivity boost for a busy developer like yourself.

In this tutorial, you'll speed up your workflow by setting up automated deployments for your app. 💨

Goals
We will end up with a workflow where the production application is in sync with the repository's main branch. In other words, the latest commit on the main branch is always the same code that runs the live application.

This is a time saver because you don't have to look up which version of your code is currently deployed. You'll always be able to refer to the main branch as the production branch.

Overview
To accomplish our goal, we need to:

  1. Add a webhook on Github which will call our endpoint each time we push to the repository
  2. Create an endpoint with Webhook that runs the redeploy script when it receives a request from Github
  3. Write a redeploy script that fetches the latest commit from Github and restarts the application

Requirements
To follow along, you'll need a:

Prefer video? In this live recording on YouTube, I go over this tutorial and set up everything completely from scratch. If you like to learn by watching others, you should definitely check it out.

Step 1 — Add a repository webhook on Github

To add a webhook to an existing repository, navigate to the Github repository page and go to "Settings" > "Webhooks". Click on "Add webhook".

Screenshot of Github repository settings with steps showing how to add a webhook

Add a webhook with the following settings:

  • Payload URL — A custom domain that points to your server or your server's public IP, followed by /hooks/ and the name of your application (e.g.: https://yourdomain.com/hooks/nodejs-app)
  • Content type — Choose application/json
  • Secret — A shared secret between Github and your server. Without it, anyone could redeploy your application by calling your endpoint. I like to use RandomKeygen to generate secure strings for this kind of stuff
  • SSL verification — Will appear only if you've filled in a payload URL that starts with HTTPS. Assuming you have a working SSL certificate for your domain, leave this option enabled
  • Which events would you like to trigger this webhook? — Default option: "Just the push event."
  • Active — Uncheck this option. We will activate the hook later on after we create the endpoint on the server

Step 2 — Create an endpoint using Webhook

Webhook is a lightweight server that allows you to easily create and configure HTTP endpoints, which you can then use to execute a script or a set of commands. We will use Webhook to listen for incoming requests from Github, and when a request is made, run our redeploy script.

Install Webhook

To install Webhook on Ubuntu, run:

sudo apt install webhook
Enter fullscreen mode Exit fullscreen mode

Note: If you get an error that says "E: Unable to locate package webhook", you need to run sudo apt update first to update the package list.

Configure Webhook endpoint

Configuring Webhook is done through a single JSON file. The file holds an array of items with each item representing an endpoint.

Create a hooks.json file in your home (~) folder:

nano ~/hooks.json
Enter fullscreen mode Exit fullscreen mode

And add the following contents:

[
  {
    "id": "nodejs-app",
    "execute-command": "/home/maxim/redeploy-nodejs-app.sh",
    "command-working-directory": "/home/maxim/nodejs-app",
    "trigger-rule": {
      "and": [
        {
          "match": {
            "type": "payload-hash-sha1",
            "secret": "yourgithubsecret",
            "parameter": {
              "source": "header",
              "name": "X-Hub-Signature"
            }
          }
        },
        {
          "match": {
            "type": "value",
            "value": "refs/heads/main",
            "parameter": {
              "source": "payload",
              "name": "ref"
            }
          }
        }
      ]
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Let's break it down and learn what each setting does:

  • id - Name of your application. Will be used to create the endpoint. For example, nodejs-app will result in the following endpoint /hooks/nodejs-app. It should match "Payload URL" from the previous step so Github calls the correct endpoint
  • execute-command - Command or script to execute when the endpoint is called. Should point to the redeploy script that we'll write in the next step
  • command-working-directory - Working directory that will be used by the script when called. Set this to the folder where your application lives
  • trigger-rule - Rule that will be evaluated before executing the script. We use the and property to specify the request should meet two criteria:
    1. It should have an X-Hub-Signature header present with the SHA1 hash of secret. This is how Github passes the secret to us so we can verify the request is legitimate. Value of secret should match "Secret" from the previous step
    2. It should have a ref property in the body that equals refs/heads/main, so we only redeploy on a push to the main branch. Change this value if your branch has a different name

Check the hook definition page for a complete list of all configuration options if you're curious.

Save the file with CTRL + O and enter. Then exit nano editor with CTRL + X.

Start Webhook and forward incoming requests

To start Webhook, run this command:

webhook -hooks ~/hooks.json &
Enter fullscreen mode Exit fullscreen mode

By default, Webhook starts a web server that listens for incoming requests on port 9000. You will need to configure a reverse proxy, like Nginx, to forward HTTP requests to Webhook.

Alternatively, you can change the "Payload URL" of the Github webhook to include the port after the domain, like so https://yourdomain.com:9000/hooks/nodejs-app. Make sure port 9000 is not blocked by the firewall.

I recommend, however, to go with the reverse proxy approach as it's generally more secure to have everything come through the HTTP (80)/HTTPS (443) ports and have all other ports closed for the public.

Below is a configuration example for Nginx that redirects all requests with destination https://yourdomain.com/hooks/... to Webhook.

server {
  listen      443 ssl http2;
  listen      [::]:443 ssl http2;
  server_name yourdomain.com;

  # SSL config
  # ...

  # Webhook reverse proxy
  location /hooks/ {
    proxy_pass http://127.0.0.1:9000/hooks/;
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to reload Nginx so it picks up the configuration changes:

sudo nginx -s reload
Enter fullscreen mode Exit fullscreen mode

Step 3 — Write redeploy script

Lastly, we'll write a script to redeploy the application. It will do the following steps in sequence:

  1. Fetch the latest code from Github
  2. Install NPM dependencies
  3. (Optional) Build step
  4. Restart the application

Create a file named redeploy-nodejs-app.sh, or give it a different name that's consistent with execute-command in hooks.json. Add the following to it:

#!/bin/sh

# 1. Fetch the latest code from remote
git pull -f origin main

# 2. Install dependencies
npm install

# 3. (Optional) Build step that compiles code, bundles assets, etc.
# npm run build

# 4. Restart application
pm2 restart nodejs-app
Enter fullscreen mode Exit fullscreen mode

You can make adjustments to the redeploy script to suit your setup however needed.

Note: If you're pulling from a private repository, you need to grant the server access to your Github account. Otherwise, the redeploy script will fail with a "Permission Denied" error.

After you save the file, let's make it executable with:

chmod +x redeploy-nodejs-app.sh
Enter fullscreen mode Exit fullscreen mode

This last step is important otherwise Webhook will fail to execute the script with a permission error.

Now go back to Github, activate the webhook, make a change in your code and push to remote. If all went well, you should see the change live after a few seconds! 🥳

Write clean code. Stay ahead of the curve.

Every other Tuesday, I share tips on how to build robust Node.js applications. Join a community of developers committed to advancing their careers and gain the knowledge & skills you need to succeed.

Subscribe for success!

💖 💪 🙅 🚩
maximization
Maxim Orlov

Posted on October 14, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related