Deploy using GitHub webhooks

A webhook is an HTTP endpoint on your server which can be called to execute tasks. For example in the case of GitHub you can setup that GitHub sends an HTTP POST each time you do something on a specific repository. When your endpoint is called, it will execute a command like running tests, deploying a new version, sending an email, or whatever you want.

It’s been a while since I wanted to do this sort of things, without using a SaaS solution to handle the webhooks. Recently I discovered the webhook library which can do exactly what I want on my server with just a few configuration.

I’m using an Ubuntu 64-bit on my server, so let’s download the corresponding binary on the releases page. Then I must upload the binary on my server in the /var/www/webhooks directory along with a hooks.json file containing an empty array [].

I want webhook to be a service on my server. To do so, as I’m using systemd, I need to create the corresponding service file webhook.service in /etc/systemd/system directory:

[Unit]
Description=Webhooks

[Service]
ExecStart=/var/www/webhooks/webhook -hooks /var/www/webhooks/hooks.json -hotreload

[Install]
WantedBy=multi-user.target

The -hooks option specifies the path to my hooks file. The -hotreload option tells to the webhook library to watch for changes in my hooks file and to reload it automatically. This option can be useful if I want to add a hook or update an existing one without restarting my service.

Then I need to run a few commands with systemctl:

  • systemctl enable webhook.service to enable the newly created service
  • systemctl start webhook.service to start the service

Now if I check the service status using service webhooks status command, I should get this:

● webhooks.service - Webhooks
   Loaded: loaded (/etc/systemd/system/webhooks.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-09-02 13:07:52 CEST; 9min ago
 Main PID: 30444 (webhook)
   CGroup: /system.slice/webhooks.service
           └─30444 /var/www/webhooks/webhook -hooks /var/www/webhooks/hooks.json -hotreload

I now have a webhook service on my server. By default it listens on 0.0.0.0:9000. But I want it to be available on a specific domain over SSL. As I’m using nginx for my other websites, I want to do the same with my webhook service. To achieve that I need to create this vhost (usually located in /etc/nginx/sites-available):

server {
  listen 443 ssl;
  server_name webhooks.my-server.com;

  ssl_certificate     /my-certificate/my-server.com/fullchain.pem;
  ssl_certificate_key /my-certificate/my-server.com/privkey.pem;

  location / {
    try_files $uri @proxy;   
  }

  location @proxy {
    proxy_pass http://webhooks;
  }
}

upstream webhooks {
  server 127.0.0.1:9000;  
}

My webhook service is finally accessible from the Internet, but it doesn’t do anything yet.

The webhook library allows some configuration to:

  • Tell in which conditions a webhook should be triggered
  • Prevent anyone else than the authorized caller to trigger the webhook

For my first webhook, I want something simple: deploying my website each time I push a new tag. To do so, I just need to update my hooks.json as follow:

[
  {
    "id": "simple-pull",
    "execute-command": "/var/www/webhooks/commands/simple-pull",
    "pass-arguments-to-command": [
      {
        "source": "payload",
        "name": "repository.name"
      }
    ],
    "trigger-rule": {
      "and": [
        {
          "match":
          {
            "type": "payload-hash-sha1",
            "secret": "mysecret",
            "parameter":
            {
              "source": "header",
              "name": "X-Hub-Signature"
            }
          }
        },
        {
          "match":
          {
            "type": "value",
            "value": "tag",
            "parameter":
            {
              "source": "payload",
              "name": "ref_type"
            }
          }
        }
      ]
    }
  }
]

The trigger-rule attribute allows defining conditions to meet before my webhook is triggered. In the example above I only check if the signature from GitHub is valid and if there is a new tag created. Then the simple-pull script is called with the repository name as an argument.

I wrote my simple-pull script that way so I can reuse it for the other MD websites to deploy them all using a simple git pull:

#! /bin/bash

cd /var/www/$1
git pull

Please note that this script requires the targetted repository to be already cloned in /var/www.

The last thing to do is to activate webhooks in the repository on GitHub. To do that I open the settings tabs and go to webhooks section to set some parameters:

  • The payload URL to https://webhooks.my-server.com/hooks/simple-pull
    • URL called by GitHub for webhooks
  • The content type to application/json
  • The secret to mysecret
    • The secret shared between GitHub and my server to ensure that it is GitHub which has sent the payload

From now on my website will be deployed each time I push a new tag 🙂

David Authier

David Authier
Developer. Freelancer. Half of MD.