DEV Community

Cover image for How to deploy your website using the Laravel Process Facade and Github Webhooks
Darrin Deal
Darrin Deal

Posted on

How to deploy your website using the Laravel Process Facade and Github Webhooks

Laravel 10 was recently released and along with that came a new facade that is an abstraction around the Symfony Process component. This facade makes it easy to run command line scripts right from Laravel. This abstraction makes the action of using Process to be more familiar to the laravel developer.

In this post I am going to walk you through how you can use this new facade along with GitHub webhooks and queue workers to run a deployment script for your site. This is a very simple way to automate your site deployments and only the beginning of what you can do with this new facade. Let’s get started in implementing this idea.

Getting Started

There are some assumptions made and topics left out intentionally to focus on task at hand. Those assumptions are that you already have a Laravel 10 project up and running on your server, that you have the infrastructure to run queue jobs, and your project is managed in a GitHub repository.

For the sake of this tutorial I will also assume that your deployments are done manually by pulling down the code via git and running the needed commands every time you deploy new code. In this tutorial we will compile those commands in a shell script to be ran by the new process facade. Let’s get started by creating a new route for our webhook.

Create A Route

Create a new function in one of our controllers or a new that will handle our webhook. I created a new controller that will hold all of our webhook code called HooksController. You can create this by running the following command.

php artisan make:controller HooksController

In the new controller or where you would like we will create the function to handle the webhook.

//1
public function handleGithubWebhookForDeploy(Request $request){
        // 2
        $requestHash = $request->header('x-hub-signature-256') ?? '';
        //3
        $payload = $request->getContent();


        // 4
        return response()->json(['success' => 'success'], 200);
}
Enter fullscreen mode Exit fullscreen mode

I added some code that we will need later here is what its doing.

We need the request object to create our hashed secrets.
We are going to pull the GitHub generated hash from the headers.
We need the json string sent to us by GitHub to generate our hash.
The command will always return a 200 unless the hashes don’t match or it’s a webhook for a different branch than we want.

We will go over these in more detail in a bit. Now in the web routes file add a new post route. Here is what I would add for the example above.

use App\Http\Controller HooksController;

Route::post('/hooks/deploy'. [HooksController::class, 'handleGithubWebhookForDeploy']);
Enter fullscreen mode Exit fullscreen mode

Create GitHub Webhook

To have Github send us a webhook when we push our code up we need to create a webhook reference in our repository’s settings page. Click on the settings link in the repository actions bar.

Image description

Once there, under Code and Automation you will se a link to webhooks. Click there and it will take you to a page where you can manage the webhooks your repo sends.

Image description

On the right hand side of the page you will see an Add Webhook button. Click the button to add your new webhook. In the form add the following.

Payload Url - The url to your site with the path. Mine would be https://example.com/hooks/deploy
Content Type - application/json
Secret - Add a random secret. Keep this for later as this is how we will secure our route.
Leave all the other defaults

Once that is all filled out you can click the add webhook. Now if you push new code to the repo and refresh that page you should see the recent webhook deliveries. This shows you the status of the webhook response and what was sent in the request. This page is helpful in debugging the route

Image description

Now that we have our webhook we can implement our handler to make sure GitHub is the only one who has access to our route.

Handle GitHub Webhook Verification

When we receive a webhook from Github it sends along a payload and headers, like every other web request. In the headers it includes a HMAC sha256 hash of our secret. To verify that GitHub sent us this request we will generate our own version of the has with our secret that we setup and the payload of the request to compare.

In our boilerplate function we got all the parts that we need to generate our hash. I recommend adding your secret to your env and pulling it in that way so you don’t commit your secret. Below the requestHash variable add the following.

$localHash = 'sha256='.hash_hmac('sha256', $payload, <Your Secret String>);

We are adding the sha256 to the beginning of the string so that it matches what we get from GitHub.
We then use the hash_hamac function to generate our hash.
The first argument it the algorithm. In our instance it is sha256
The second is our key. Github uses the json string of the payload.
The third argument is the secret. I recommend pulling this from the env or config.

We will use this hash to verify the Webhook Request. Let’s add the following below to run our check.

//1
if(!hash_equals($localHash, $requestHash)){            
    return abort(404);        
}
//2

$json = json_decode($payload);        
if( $json->ref != "refs/heads/main"){
    return response()->json(['success' => 'not-main'], 200);        }
Enter fullscreen mode Exit fullscreen mode

We are checking if the hashes match. We use the hash_equals function to protect against timing attacks. We also are returning a 404 if the hashes don’t match. That means someone, not GitHub, is sending the request.
We are converting the json string to something useable and checking which branch the webhook was generated for. In this example we are using main but this could be for whatever branch you would like. If it’s not that branch we return 200 and call it a day.

It’s important to return a 200 to GitHub because if you don’t they will stop sending you webhooks and you will be back to deploying code by hand. Let’s move on to creating our deployment script.

Create A Deployment Script

This is the easiest part of this tutorial. Somewhere on your server create a new sh file. In this file you will want to include all the steps that you would manually do in a deployment. Here is an example.

git pull origin main
php artisan migrate --force
...
Enter fullscreen mode Exit fullscreen mode

Keep track of where this file is on the server. You will need it in the final step.

Setup Queue Job To Handle The Deployment Step

One small hiccup in this process is that the GitHub webhook expects a near instant response. In my experience the deployment script takes longer that GitHub likes. To handle this we need to offload the action of running the deployment script. The solution that I came up with was to handle that in a queue job. Laravel has some great documentation on how to set up your queue works. I recommend looking through that if you don’t already have a queue worker up and running. Let’s create a job that we can dispatch in our webhook handler. Run the following artisan command.

php artisan make:job ProcessDeplymentWebhook

This will create a new file for us that we will fill out in the next section. For now let’s have our route dispatch a new job when our webhook handler is called. In our route handler add the following:

At the top
use App\Jobs\ProcessDeplymentWebhook;

And below the branch check in out webhook handler
ProcessDeplymentWebhook::dispatch();

This will dispatch our queue job and allow us to bypass the slowness of running our deployment steps. Here is our final route handler.

Use The Process Facade

We have our webhook setup and our queue to handle the deployment the only item left is to call our deployment script in the queue. To do this we will use the new Process facade in Laravel 10. Like I said the is an abstraction around the Symfony Process component. The facade makes it super simple to use. Let’s autoload the facade into our queue job class with the following.

use Illuminate\Support\Facades\Process;

In the handle function we will add the following:

//1
$script = "bash " . config('app.deploy_script'); 

//2
Process::run($script);
Enter fullscreen mode Exit fullscreen mode

Here we are creating the script we want to run. Think of this as the command you would type into the terminal if you were running this manually.
Finally, we call the run method on the Process facade. This will complete and end our job.

Super simple right! You can also add a function to log out the output in realtime if you would like. This is one simple way that we can use this facade and I am excited to see more and more going out of the community in the future with the process facade.

Where To Go Next

That’s it! Checkout out the documentation for this new facade and see all the things you can do with it. I am really excited to see what the community does with this. What ideas do you have with using this facade?

This post was originally posted on my website here

Top comments (0)