Updated on December 14th, 2021 to refine our GitHub auto-deploy solution.
Most of my time is dedicated to dev projects that make the lives of marketers easier: websites, apps, tools, scripts, etc. Today, however, I’m writing for web devs in an attempt to make their lives easier. And their solutions smarter. In an effort to streamline development updates to a code base in a staging or production environment, we have created an auto-deploy setup guide for any GitHub (github.com) repository.
If you’re wondering what Git is, you’re a bit behind, as it has taken over the code versioning tool for developers. If you’re not a developer, Git is still super useful as a tool that records all revisions to any file you want to track. Collaborating with some marketing colleagues and technical experts on a giant content project? Git can manage everyone’s drafts with the same great versioning control. This article has more good information about Git.
Why An Auto-Deploy Solution?
The better question is probably, “why not?” If set up correctly, it makes a developer’s life easier. There are fewer points of failure when deploying code. It saves a considerable amount of time. And, it’s slick. You’ve finished making updates to your app in your local dev environment and are ready to stage and test. You commit and push your changes to the staging branch of your Git repo. Your auto-deploy setup grabs those latest updates and deploys them on your staging site almost instantly. You test your updates. They look solid; you’re ready to deploy live. Back in your local dev environment, you merge your staging branch updates with the master (or production) branch. You commit and push your updates. Again, auto-deploy handles the rest. All you have to do is (clear the cache and) test.
Some argue that Git should not be used as a deployment tool because it does not properly handle permissions or track empty directories. But, if set up correctly with Git commands being executed by the Apache/Nginx user, it seems to be a solid solution for many applications, especially compared to other confusing and expensive deployment tools.
Marketers: you may want to stop here and just send this to your developer(s). What comes next may not make much sense.
What you will need:
- A server to host your site. Our example is a Ubuntu (Linux) server running either Apache or Nginx and PHP
- SSH access to your server with sudo/root privileges
- A GitHub.com account and repository
1. On the Web Server
Here we install and set up Git on the server. We also create an SSH key so the server can talk to GitHub without using passwords.
Install Git
sudo apt-get update
sudo apt install git
If you already had Git installed, make sure it’s a relatively new version – upgrade it to the latest if need be.
git --version
Set Up Git
Next, we will configure Git. In general, these values don’t mean much, but best to make them descriptive.
git config --global user.name "[some user-name]"
git config --global user.email "[your github email]"
Create An SSH Directory for the Apache/Nginx User
You can find out what user controls the Apache or Nginx processes by looking in their respective config files. We’re using the www-data user and group in our example.
sudo mkdir /var/www/.ssh
sudo chown -R www-data:www-data /var/www/.ssh/
Generate a Deploy Key for Apache/Nginx User
Next, we want to generate a deploy key that we can add to the GitHub repo. We will be using the ssh-keygen command. The command below instructs the system to create an RSA key that belongs to the www-data user. You don’t need a passphrase, so be sure to leave it blank during the process of generating the key.
sudo -Hu www-data ssh-keygen -t rsa
Once generated, print out the key and copy it to your clipboard.
sudo cat /var/www/.ssh/id_rsa.pub
2. On Your Origin (github.com)
Here we add the SSH key to the origin to allow your server to talk without passwords.
Add the SSH Key to the Repo
- https://github.com/[githubname]/[repo]/settings/keys
- Create a new key and name it appropriately
- Paste the deploy key you generated on the server and save
3. On the Web Server
Now that we have the deploy key installed, we are ready to clone the repo on our web server.
Clone the Repo
Here we clone the repo into a chmodded /var/www/[site_dir] folder. Note that we switch to the www-data user before running the git clone command. This is an important step because the deploy key we generated is owned by the www-data user and it will only work for that user, even if you are on the root.
cd /var/www
sudo chown -R www-data:www-data /var/www/[site_dir]
sudo su www-data
git clone [email protected]:[githubuser]/[gitrepo].git /var/www/[site_dir]
- or for branch -
git clone -b [branch_name] [email protected]:[githubuser]/[gitrepo].git /var/www/[site_dir]
exit
4. Auto-Deployment
If you’ve made it this far, you are almost ready. All the steps up to this point should have your web server properly communicating with your GitHub repo. You should be able to drill into your site directory, switch to your Apache/Nginx user (i.e., www-data), and run Git commands like you normally would. In fact, this is good practice in getting familiar with doing so in case you have to fix any conflicts, etc.
Deployment Rules
I did more research on other approaches to a Git auto-deploy solution, and found this great unassuming article. Not only did it shed more light on why git pull
should not be used as a deployment tool, but the “deployment rules” section was spot on for what I was trying to ultimately accomplish. I pared down the rules to what I consider a good fit for my needs:
- All files in the branch being deployed should be copied to the deployment directory.
- Files that were deleted in the Git repo since the last deployment should get deleted from the deployment directory.
- Any changes to tracked files in the deployment directory after the last deployment should be ignored when following rules one and two.
- Untracked files in the deploy directory should be left alone.
Option 1 – Cron
If the site is password protected, a webhook won’t work. Or, if you don’t want to bother with a webhook (instructions below), you can set up a cronjob that will run pull commands as often as you’d like.
The deploy article suggests a handful of possible solutions that fulfill the deployment rules. After some trial-and-error testing, I settled on using two sequential commands:
git fetch --all
This command fetches all data from the remote without trying to merge or rebase. More on git fetch.
git checkout --force "origin/master"
This command, utilizing the “force” option, tells Git to proceed even if the index or the working tree differs from HEAD. Essentially, this overwrites local changes (if any), so the local repo mirrors the remote repo, which specifically satisfies deployment rule number two. The rest of the built-in git checkout
functionality satisfies the rest of our deployment rules, so all four are addressed. More on git checkout.
Using cron as our auto-deployment method, the instructions below will switch to the Apache/Nginx user (i.e., www-data), traverse to the site directory, and run the git fetch and git checkout commands — every minute.
*/1 * * * * su -s /bin/sh www-data -c 'cd /var/www/[site-dir] && git fetch --all && git checkout --force "origin/master"'
Option 2 – Webhook
Another option is setting up a webhook, which is a feature that GitHub allows for every repository. A webhook is simply a URL that GitHub will hit anytime an update is pushed to the origin. We can couple this functionality with a deployment script that will run Git commands to PULL from the origin and update your code base on your web server.
Create the Deployment Script for Your Site
Copy/paste the PHP code below and save it as ‘deploy.php’ in your site root. You will be adding this file to your repo, so you can do it in your local dev environment or on the live web server—as long as it gets added and is available at [yoursite].com/deploy.php
<?php /** * GIT DEPLOYMENT SCRIPT * * Used for automatically deploying websites via GitHub * */ // array of commands $commands = array( 'echo $PWD', 'whoami', 'git pull', 'git status', 'git submodule sync', 'git submodule update', 'git submodule status', ); // exec commands $output = ''; foreach($commands AS $command){ $tmp = shell_exec($command); $output .= "<span style=\"color: #6BE234;\">\$</span><span style=\"color: #729FCF;\">{$command}\n</span><br />"; $output .= htmlentities(trim($tmp)) . "\n<br /><br />"; } ?> <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <title>GIT DEPLOYMENT SCRIPT</title> </head> <body style="background-color: #000000; color: #FFFFFF; font-weight: bold; padding: 0 10px;"> <div style="width:700px"> <div style="float:left;width:350px;"> <p style="color:white;">Git Deployment Script</p> <?php echo $output; ?> </div> </div> </body> </html>
Add, commit, and push this to GitHub
git add deploy.php
git commit -m 'Adding the git deployment script'
git push
Set Up Service Hook
Now, in your GitHub.com repo settings, we will set up the webhook which will automatically call the deploy URL, thus triggering a PULL request from the server to the origin.
- https://github.com/[githubname]/[repo]/settings/hooks
- Click Add webhook to add a service hook
- Enter the URL to your deployment script as the Payload URL – http://[yoursite].com/deploy.php
- Leave the rest of the options as default. Make sure ‘Active’ is checked.
- Click Add webhook
Ready to Go
At this point, you should be all set! You and your team can make code updates and push them to the origin, and they will automatically be pulled to your web server’s code base.
Some Notes
- Some servers may require slightly different instructions. Check out this resource: https://docs.github.com/en/authentication/connecting-to-github-with-ssh
- Navigate the deployment script to trigger a pull and see the output:
- http://[yoursite].com/deploy.php (this is useful for debugging)
- When you push to the origin (GitHub), your site will automatically ping the above url (and pull your code)