Deploy app the Heroku way with Git hooks
With Git hooks, you can do some cool stuff such as checking code syntax, running tests before committing, sending emails to other people after a commit is made… or even deploying your application. Ever wondered how Heroku make all the magic happen behind the git push command? You can do that too (not exactly as they do but still, you know). Let me show you how simple it is.
1. Create a bare git repository
Let’s say, your Rails application path is in `/home/webapp/webapp`. Now let’s create another directory:
webapp@server$ cd /home/webapp/
webapp@server$ mkdir webapp && cd webapp && git init
webapp@server$ cd ..
webapp@server$ mkdir webapp.git && cd webapp.git && git init --bare
Initialized empty Git repository in /home/webapp/webapp.git
New directory `webapp.git` would look like this:
webapp.git/
|-- branches
|-- config
|-- description
|-- HEAD
|-- hooks
|-- info
|-- objects
`-- refs
Then we create a new file named `post-receive` in `webapp.git/hooks/` with the following lines:
#!/bin/bash
unset GIT_DIR
RAILS_ENV="development"
PID_FILE="/home/webapp/webapp/tmp/pids/puma.pid"
SOCK_FILE="/home/webapp/webapp/tmp/sockets/puma.sock"
DAEMON_OPTS="-C /home/webapp/webapp/config/puma.rb"
APP_REPO="/home/webapp/webapp.git"
while read oldrev newrev ref
do
branch=`echo $ref | cut -d/ -f3`
if [ "$branch" == "master" ] ; then
cd /home/webapp/webapp/
echo "--------> Getting latest code..."
git pull $APP_REPO master
echo "--------> Running bundle install..."
bundle install --without development test --deployment
echo
echo "--------> Precompiling assets..."
bundle exec rake assets:precompile RAILS_ENV=$RAILS_ENV
echo
echo "--------> Running migrations..."
bundle exec rake db:migrate RAILS_ENV=$RAILS_ENV
echo
echo "--------> Restarting web server..."
if [ -f $PID_FILE ]; then
kill -KILL $(cat $PID_FILE)
fi
rm -f $SOCK_FILE
RAILS_ENV=$RAILS_ENV bundle exec puma $DAEMON_OPTS
fi
done
The `post-receive` file is a Bash script which is rather simple: it jumps into the application folder, performs a git pull, then installs necessary gems, compiles assets, runs migrations, then restarts the web server (Puma in our case).
Don’t forget to make the script executable:
webapp@server $ chmod 755 webapp.git/hooks/post-receive
2. Add your public key to webapp’s authorized key file
(This is done so that we won’t have to enter our password whenever we push commits)
you@your-machine $ cat ~/.ssh/id_rsa.pub | ssh webapp@server-ip-or-domain "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 /home/webapp/.ssh/authorized_keys"
3. Let the fun begin:
From the application folder in your local machine, we add our new git repository url:
you@your-machine$ git remote add myoku webapp@server-ip-or-domain:webapp.git
Finally, to deploy our code:
you@your-machine$ git push myoku master
Counting objects: 1, done.
Writing objects: 100% (1/1), 182 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
remote: --------> Getting latest code...
remote: From /home/webapp/webapp
remote: * branch master -> FETCH_HEAD
remote: Updating eefb222..daa93c0
remote: Fast-forward
remote: --------> Running bundle install...
remote: Using rake (10.4.2)
remote: Using i18n (0.7.0)
remote: Using json (1.8.2)
remote: Using minitest (5.5.1)
remote: Using thread_safe (0.3.5)
remote: Using tzinfo (1.2.2)
remote: Using activesupport (4.1.6)
remote: Using builder (3.2.2)
remote: Using erubis (2.7.0)
remote: Using actionview (4.1.6)
remote: Using rack (1.5.2)
remote: Using rack-test (0.6.3)
remote: Using actionpack (4.1.6)
remote: Using mime-types (2.4.3)
remote: Using mail (2.6.3)
remote: Using actionmailer (4.1.6)
remote: Using activemodel (4.1.6)
remote: Using arel (5.0.1.20140414130214)
remote: Using activerecord (4.1.6)
remote: Using coffee-script-source (1.9.1)
remote: Using execjs (2.4.0)
remote: Using coffee-script (2.3.0)
remote: Using thor (0.19.1)
remote: Using railties (4.1.6)
remote: Using coffee-rails (4.0.1)
remote: Using hike (1.2.3)
remote: Using jquery-rails (3.1.2)
remote: Using multi_json (1.11.0)
remote: Using puma (2.9.1)
remote: Using bundler (1.3.5)
remote: Using tilt (1.4.1)
remote: Using sprockets (2.12.3)
remote: Using sprockets-rails (2.2.4)
remote: Using rails (4.1.6)
remote: Using sass (3.2.19)
remote: Using sass-rails (4.0.5)
remote: Using sqlite3 (1.3.10)
remote: Using turbolinks (2.5.3)
remote: Using uglifier (2.7.1)
remote: Your bundle is complete!
remote: Gems in the groups development and test were not installed.
remote: It was installed into ./vendor/bundle
remote:
remote: --------> Precompiling assets...
remote: I, [2015-03-22T07:55:16.419290 #2581] INFO -- : Writing /home/webapp/webapp/public/assets/application-758ba4c9d6d8c456498dd600e7a83fcf.js
remote: I, [2015-03-22T07:55:16.461980 #2581] INFO -- : Writing /home/webapp/webapp/public/assets/application-42324ba53658078aed3e1f679cc258fc.css
remote:
remote: --------> Running migrations...
remote:
remote: --------> Restarting web server...
remote: hooks/post-receive: line 26: kill: (30026) - No such process
remote: Puma starting in single mode...
remote: * Version 2.9.1 (ruby 1.9.3-p484), codename: Team High Five
remote: * Min threads: 16, max threads: 32
remote: * Environment: development
remote: * Listening on unix:///home/webapp/webapp/tmp/sockets/puma.sock
remote: * Daemonizing...
To [email protected]:webapp.git
eefb222..daa93c0 master -> master
I made an example with Vagrant and Ansible here: https://github.com/minhdanh/gitdeploy-ansible. The sample Rails app uses Puma and nginx as the web servers. Check it out!