Need to self-host your front end away from the "modern" services like Netlify or Vercel? As both continue to get a little sketchier with time, it's definitely something I'm having to consider. Deploying a static site to an old-school host (like my favourite: Krystal) is easy enough, but you lose that wonderful "rebuild on Git push" that we've all become accustomed to. Or do you?
Whenever I push an update in my websites master branch in GitHub, it notifies my server through something called a webhook. My server pulls the latest version of my site from GitHub, runs a Hugo build, and moves the built files into my live site directory.
Chris has written up how to achieve similar functionality on Digital Ocean. I'm not sure how well this would translate to other services, but the toolchain seems generic enough. You need:
- A GitHub account;
- Some form of PHP hosting with control over file and folder placement and SSH/CLI access;
And that seems to be about it.
The steps look like this:
- Set up your repo on GitHub;
- Install whatever build tool you want to use on your server (Chris uses Hugo, but any front-end framework should work the same way);
- Create an SSH key for the server;
- Clone your GitHub repository to the server via SSH:
git clone git@github.com:<YOUR_USERNAME>/<YOUR_RESPOSITORY>.git
- Create an automation script within a PHP file; Chris uses
deploy.php
and the code below; - Add a deploy secret as an environment variable (likely in the
htaccess
file); - On GitHub, set up a webhook with the "payload URL" as your domain plus the name of the PHP script, e.g:
theadhocracy.co.uk/deploy.php
; - Test it out.
Here's the code (note Chris' version may be more up to date, just saving here in case that post disappears):
<?php /** * Automated deploy from GitHub * * https://developer.github.com/webhooks/ * Template from ServerPilot (https://serverpilot.io/community/articles/how-to-automatically-deploy-a-git-repo-from-bitbucket.html) * Hash validation from Craig Blanchette (http://isometriks.com/verify-github-webhooks-with-php) */ // Variables $secret = getenv('GH_DEPLOY_SECRET'); $repo_dir = '/srv/users/serverpilot/apps/<YOUR_APP>/build'; $web_root_dir = '/srv/users/serverpilot/apps/<YOUR_APP>/public'; $rendered_dir = '/public'; $hugo_path = '/usr/local/bin/hugo'; // Validate hook secret if ($secret !== NULL) { // Get signature $hub_signature = $_SERVER['HTTP_X_HUB_SIGNATURE']; // Make sure signature is provided if (!isset($hub_signature)) { file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: HTTP header "X-Hub-Signature" is missing.' . "\n", FILE_APPEND); die('HTTP header "X-Hub-Signature" is missing.'); } elseif (!extension_loaded('hash')) { file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Missing "hash" extension to check the secret code validity.' . "\n", FILE_APPEND); die('Missing "hash" extension to check the secret code validity.'); } // Split signature into algorithm and hash list($algo, $hash) = explode('=', $hub_signature, 2); // Get payload $payload = file_get_contents('php://input'); // Calculate hash based on payload and the secret $payload_hash = hash_hmac($algo, $payload, $secret); // Check if hashes are equivalent if (!hash_equals($hash, $payload_hash)) { // Kill the script or do something else here. file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Bad Secret' . "\n", FILE_APPEND); die('Bad secret'); } }; // Parse data from GitHub hook payload $data = json_decode($_POST['payload']); $commit_message; if (empty($data->commits)){ // When merging and pushing to GitHub, the commits array will be empty. // In this case there is no way to know what branch was pushed to, so we will do an update. $commit_message .= 'true'; } else { foreach ($data->commits as $commit) { $commit_message .= $commit->message; } } if (!empty($commit_message)) { // Do a git checkout, run Hugo, and copy files to public directory exec('cd ' . $repo_dir . ' && git fetch --all && git reset --hard origin/master'); exec('cd ' . $repo_dir . ' && ' . $hugo_path); exec('cd ' . $repo_dir . ' && cp -r ' . $repo_dir . $rendered_dir . '/. ' . $web_root_dir); // Log the deployment file_put_contents('deploy.log', date('m/d/Y h:i:s a') . " Deployed branch: " . $branch . " Commit: " . $commit_message . "\n", FILE_APPEND); }