Sunday, 5 July 2020
Setting up a fresh server for Continuous Deployment with Git
We all want to be able to focus on writing code, without wasting time when deploying changes to production.
But it's not that simple. There are several operations our code needs to go through going from development to production (I'm referring to a Laravel Application, but it will be similar for most web applications):
- Push our new code to the server.
- Update our Javascript & PHP dependencies.
- Compile our Javascript and CSS.
- Finally, we need to migrate the production database.
Doing all these operations manually before every deployment quickly gets too time consuming. Besides, it's a repetitive task that should be automated...
So for quite a while I researched & tried to find the best way to do this. At first my solution was using GitHub webhooks, but that solution had it's problems.
Last week, I finally found the perfect solution, which lets me deploy the code to production with just 2 keystrokes: gp
π.
How it works
We're going to use Git (version control software). Git has several hooks that it can call after different stages automatically. We'll use the post-receive hook which is called after your repository has received pushed code.
A Hook is a program you can place in a hooks directory to trigger actions at certain points in gitβs execution.
In this script we'll do all the above operations.
1. Login to your remote server
Open your terminal and login to your server using the following command:
ssh <your_user>@<server_ip_address>
Make sure that you have Git installed on the server.
2. Create a folder for your code
The source code for your application needs to be put somewhere. By convention, code goes inside the /var/www
directory. Navigate there using:
cd /var/www
Now, create a new folder to put the source code. For this tutorial, it will be called app-folder
.
mkdir app-folder
Now let's remember the full path to our app-folder is /var/www/app-folder/
.
3. Initialize a git repository in your home folder
We'll create a git repository in our users' home folder. Let's create a repo
folder to hold all our future repos:
mkdir -p ~/repo/my-app.git
Now, we'll navigate into our repo folder, and initialize a bare git repo:
cd ~/repo/my-app.gitgit init --bare
Congratulations, we now have the git repo ready for use!
4. Create hook
After initializing the git repository, new folders should appear inside ~/repo/my-app.git/
. Navigate to the hooks
directory, and create a new file called post-receive
using your preferred text editor.
cd hooksvim post-receive
Make sure to spell post-receive
right, because I misspelled it - post-recieve
, and it took me a while to figure out why it wasn't working π...
Now for the serious part: we're going to write the program to deploy our application.
This is what my post-receive
script ended up looking like (as mentioned above - this is for a Laravel app):
#!/bin/bashTARGET="/var/www/app-folder"GIT_DIR="/home/<username>/repo/my-app.git"BRANCH="master"SLACK_POST_URL="https://hooks.slack.com/services/<YOUR_SLACK_KEY_HERE>"while read oldrev newrev refdo # only checking out the master (or whatever branch you would like to deploy) if [ "$ref" = "refs/heads/$BRANCH" ]; then cd $TARGET pwd echo "activating maintenance mode" php artisan down echo "Ref $ref received. Deploying ${BRANCH} branch to production..." git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH echo "Running NPM build command" npm install npm run build echo "running composer install" /usr/local/bin/composer.phar install --no-interaction --no-dev --prefer-dist echo "running migrations" php artisan migrate --force echo "going back live..." php artisan up # Send a Slack notification when deploy finished curl -X POST --data-urlencode "payload={'channel': '#deployments', 'username': 'yiddishe-bot', 'text': 'Successfully deployed to <https://YOUR_URL_HERE/|My Site>!', 'icon_emoji': ':yiddishe-kop:'}" $SLACK_POST_URL else echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server." fidone
The code is pretty self explanatory, as it is echo
ing out what it's doing.
Update (Dec 2020): I added the commands to send a Slack notification when the deploy is ready, which is optional.
Save the file, and make it executable by running this command:
chmod +x post-receive
The work on your server is done.
5. Push local code to the server
Back on our local machine, navigate to your project folder, and initialize a git repo (if it isn't already). Then we need to add our remote server as a remote:
git remote add <name-of-remote> ssh://<username>@<server_ip_address>/<path_to_git_directory># For our example:git remote add azure ssh://<username>@<server_ip_address>/home/<username>/repo/my-app.git
I called my remote azure
, as it's an Azure server.
Now, whenever you want to deploy your code, just run:
git push <name-of-remote> <name-of-branch># we can omit the branch name - it will default to mastergit push azure
One second, this is more than 2 keystrokes!
Oh, you're right... to achieve that make your remote the default upstream, by running:
git push --set-upstream azure
Then you can deploy to production by running:
gp
It works, because gp
is an alias to git push
(if it isn't, you can add this alias yourself).
If you don't want to set the remote-server as the default upstream (you may want GitHub to be the default), then you can leave the default upstream to be origin
(GitHub) and deploy to your server by running gp azure
.
You even get all the output right into your local terminal! Here is the deployment in progress:
Summary
I'm writing this for my future reference, as this is so easy to setup, and makes deploying to production a breeze.
Update (10 Nov 2020)
I created a complete server setup script, that will get you from a clean Ubuntu server to a full Laravel deployment environment in 5 minutes! π The code is on GitHub, check it out!
Sidenote
If you get errors relating to permissions, and to avoid having to use sudo
in the web directory on your server, here is a solution with security in mind:
Add yourself to the www-data
user group, and set the setgid bit on the /var/www
directory such that all newly created files inherit this group as well.
# add yourself to the www-data groupsudo gpasswd -a "$USER" www-data# apply the ownership to the /var/www foldersudo chown -R "$USER":www-data /var/www# Correct previously created files (assuming you to be the only user of /var/www):find /var/www -type f -exec chmod 0660 {} \\\\;sudo find /var/www -type d -exec chmod 2770 {} \\\\;
See this article for more on setting the right permissions in a Laravel app.