Monday, 4 May 2020
Setup your own Laravel CD with GitHub webhooks
I recently discovered the Laravel framework - and I really like it. As I was learning the framework, I wanted to build something with it, as the best way to learn something is by building something with it. So I built a Torah learning platform, which you can visit now...
Once the application was ready to be deployed, I deployed it to an AWS server. But the process was not as simple as I would like it (I was coming from the frontend - where we have experiences like Netlify where you just push to deploy). After a commit, I would need to SSH into the server, and manually run a git pull
and php artisan migrate
...
So I researched around for a solution, and I found that I could use GitHub webhooks for that. I struggled quite a while until I got it to work, so I'm documenting it here for future reference.
Basic Overview
Here is a basic overview how it will work:
- When you push a change to GitHub, GitHub will send a POST request to a deploy endpoint on your server.
- The server will verify that the request is really from GitHub...
- The server will then run a script that will perform the git pull and all that's needed to update the application.
Setting up the Deploy mechanism on your server
This needs a few parts:
- The
/deploy
route. - The
DeployController
. - The bash script that will run the tasks.
First let's define the route (in web.php):
Route::post('deploy', 'DeployController@deploy');
Next, let's first create the DeployController (location: app/Http/Controllers):
<?phpnamespace App\\Http\\Controllers;use Illuminate\\Http\\Request;use Symfony\\Component\\Process\\Process;class DeployController 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(['./deploy.sh'], $root_path); $process->run(function ($type, $buffer) { echo $buffer; return response()->json([ 'buffer' => $buffer, 'type' => $type, ]); }); } else { return response()->json(['result' => 'failed']); } }}
We also need a deploy_secret
in the app config (config/app.php):
'deploy_secret' => env('APP_DEPLOY_SECRET'),
Now add the actual secret to the .env
file (should be a long random string):
APP_DEPLOY_SECRET=hfiluefhofwkiuehf74yor374o87r3h4o8hr3o4rh8hfpqilwjdwliuefhlw8f9384i3uh4fi
The last thing is to exclude the deploy route from CSRF verification, by adding the route to the $except
array in app/Http/Middleware/VerifyCsrfToken.php
:
protected $except = [ '/deploy',];
Now let's create the bash script that will actually run the tasks. Create a deploy.sh
file in the root of your project, like so:
#!/bin/bash# activate maintenance modephp artisan down# update source codegit reset --hard HEADgit pull# I couldn't get the next line to work - permissions issues...# npm run build# update PHP dependencies# composer install --no-interaction --no-dev --prefer-distphp artisan migrate --force# --force Required to run when in production.# stop maintenance modephp artisan up
Finally, we're done with our server. Now we just need to setup the webhook trigger on GitHub!
Setting up the Webhook on GitHub
Go to your repo's settings page, into the webhooks section. Add a new webhook:
GitHub repo webhooks section
For Payload URL insert your deploy route, which I set to /deploy
. For the secret (which will be used to verify the authenticity of the request) use the same hash you added to your .env
file above. Lastly, set only push
events to trigger this webhook.
webhook settings
Save the webhook, and now we can just run git push locally, and within a few seconds your live application should be updated! 😎
If you run into issues, you can check the webhooks recent deliveries on GitHub, and inspect the request & response to see what went wrong.
Summary
This makes a very good developer experience - write some code, then just push to deploy. But there's still room for improvement. I don't know if you noticed, in the deploy.sh there's a line that's commented out:
# I couldn't get the next line to work - permissions issues...# npm run build
Ideally I would also like to run npm run build on the server (saving me from building locally before every push), but I couldn't get it to work. I was getting permission errors, and couldn't figure out how to solve them. I chmod
'ed and chown
'ed for hours, to no avail... I think it's got to do with the node installation on my server, but I don't know for sure... If you know of a solution, please let me know!
Edit (5 Jul 2020): I just published another article with a revised way of doing this, which fixes all issues!