Laravel: automated deployment using a GitHub webhook

What

Make a git push to GitHub deploy the new modifications to a remote server.

How it works

  • GitHub sends a POST request to a specific URL on the server
  • That URL triggers the execution of a deployment shell script.

Deployment shell script

First, create the shell script. At the root of your Laravel application, add deploy.sh:

#!/bin/sh

# activate maintenance mode
php artisan down

# update source code
git pull

# update PHP dependencies
export COMPOSER_HOME='/tmp/composer'
composer install --no-interaction --no-dev --prefer-dist
	# --no-interaction	Do not ask any interactive question
	# --no-dev		Disables installation of require-dev packages.
	# --prefer-dist		Forces installation from package dist even for dev versions.

# update database
php artisan migrate --force
	# --force		Required to run when in production.

# stop maintenance mode
php artisan up

Make it executable.

chmod 777 deploy.sh

Commit and deploy.

Create a GitHub webhook

On GitHub, on your repository page, select the Settings tab, then Webhooks in the left navigation. Or go directly to the URL:

https://github.com/<your account>/<your repository>/settings/hooks

Click Add webhook:

  • Payload URL: http://<your-server.com>/deploy
  • Secret: A long random string. You'll also need it in the next section.

Add the webhook secret to Laravel

In config/app.php, add:

'deploy_secret' => env('APP_DEPLOY_SECRET'),

In your .env file, add your webhook secret:

APP_DEPLOY_SECRET=dfsdfdsafsdjkljljljlkj

Controller method

Create a controller method to launch the deployment shell script.

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Symfony\Component\Process\Process;
 
class UtilController extends Controller
{
    public function deploy(Request $request)
    {
        $githubPayload = $request->getContent();
        $githubHash = $request->header('X-Hub-Signature');
 
        $localToken = config('app.deploy_secret');
        $localHash = 'sha1=' . hash_hmac('sha1', $githubPayload, $localToken, false);
 
        if (hash_equals($githubHash, $localHash)) {
            $root_path = base_path();
            $process = new Process('cd ' . $root_path . '; ./deploy.sh');
            $process->run(function ($type, $buffer) {
                echo $buffer;
            });
        }
    }
}

Notes:

  • the HTTP header X-Hub-Signature is verified to make sure the request comes from GitHub. It's a hash of the request body (payload) using the webhook secret.
  • the Process class, included with Laravel is used to easily launch the shell script.

Add a route for the controller method

Let's add the route used by the GitHub webhook and link it to the controller method.

In routes/api.php, add:

Route::post('deploy', 'UtilController@deploy');

In app/providers/RouteServiceProvider.php, to remove the need for /api as URL prefix, change the function mapApiRoutes() to:

protected function mapApiRoutes()
{
    Route::middleware('api')
         ->namespace($this->namespace)
         ->group(base_path('routes/api.php'));
}

Commit and deploy.

Notes:

  • we use api.php instead of web.php to avoid the CSRF token verification.
  • removing the need for the /api prefix is just a personal choice :)

Change the Unix group of your web folder to www-data

This was necessary on my production server for the shell script to run properly (for Apache to be allowed to update the web folder).

sudo chgrp -R www-data .

Conclusion

That's it! The different code snippets are also available there: https://github.com/jeromejaglale/laravel-github-webhook

Feedback