clermontech-workshop-git-deploy icon indicating copy to clipboard operation
clermontech-workshop-git-deploy copied to clipboard

Material for my Git Workshop.

Clermont'ech Workshop Git

The aim of this (very short) workshop is to learn various ways to deploy with Git. We are going to cover:

  • Deploy Using Git Push/Hook
  • Deploy With Heroku
  • Deploy With Dokku
  • Capistrano

1. Deploy Using Git Push/Hook

We need two separate folders to simulate two different servers:

mkdir {local,remote}-server

In the first folder, let's create an app:

cd local-server
mkdir app
cd !$
git init
echo "Hello, World" > index.html
git add index.html
git commit -m "Initial commit"

Let's add a new remote that points to the production server:

git remote add production /absolute/path/to/remote-server/www

Now, we are going to configure the remote server.

cd ../../remote-server
mkdir www
cd !$
git init

Let's edit the .git/config file, adding the content below to allow push on the current branch:

[receive]
    denyCurrentBranch = false

Now, let's create a post-receive hook with the following content:

#!/usr/bin/env bash

set -eo pipefail

readonly LOGFILE="/tmp/deploy-app.log"

function notify()
{
    echo "Hi! I am your Git deployer, and I am deploying branch \"$1\" right now :-)"
}

function log()
{
    [[ -f "$LOGFILE" ]] || touch "$LOGFILE"

    echo "$2 ($3) successfully deploy branch: $1" >> "$LOGFILE"
}

function main()
{
    # STDIN: oldrev newrev ref
    while read _ newrev ref
    do
        local branch=$(echo "$ref" | cut -d/ -f3)

        # Abort if there's no update, or in case the branch is deleted
        if [[ -z "${newrev//0}" ]] ; then
            exit
        fi

        if [[ "$branch" ]] ; then
            notify "$branch"

            cd ..
            unset GIT_DIR GIT_WORK_TREE

            git checkout "$branch" &> /dev/null
            git reset --hard &> /dev/null

            local author_name=$(git log -1 --format=format:%an HEAD)
            local author_email=$(git log -1 --format=format:%ae HEAD)

            log "$branch" "$author_name" "$author_email"
        fi
    done
}

main "$@"

Make it executable:

chmod +x .git/hooks/post-receive

This hook is all you need to perform steps at "deploy time".

Let's test it!

cd ../../local-server/app
git push production master

Enjoy:

ls -l /absolute/path/to/remote-server/www
tail /tmp/deploy-app.log

Note: git-deploy (almost) does the same thing, but probably better.

More on this hook

One may want to send emails to the person who just deployed. That is why author_name and author_email are retrieved.

Let's try to change the author's email:

echo "<h1>Hello, World</h1>" > index.html
git add !$
[email protected] git commit -m "Surround title with html tags"
git push production master

Deploying to another branch

We can deploy another branch:

git checkout -b feat-content
echo "<p>content</p>" >> index.html
git add !$
git commit -m "add content"
git push production feat-content

Result:

tail /tmp/deploy-app.log
cat /absolute/path/to/remote-server/www/index.html

2. Deploy With Heroku

We are going to deploy a Node.JS application, that is a Chat Example.

Deploy

Heroku Toolbelt

Install the Heroku Toolbelt.

Then:

heroku login
heroku git:clone -a <heroku-app-id>
cd !$

Make changes, then deploy:

git add .
git commit -m "Make it better"
git push heroku master

3. Deploy With Dokku

Dokku, Docker powered mini-Heroku in around 100 lines of Bash.

Clone the Chat Example:

git clone git://github.com/hunterloftis/chat-example.git

Configure SSH to use the provided keys:

Host dokku.clermontech.org
  Hostname 178.62.80.95
  User dokku
  PreferredAuthentications publickey
  IdentityFile    ~/.ssh/clermontech_git_rsa

Now, let's add a new remote pointing to the Dokku server:

git remote add deploy [email protected]:your-app-name

Deploy:

git push deploy master

Browse http://your-app-name.dokku.clermontech.org.

4. Capistrano

Capistrano, a remote server automation and deployment tool written in Ruby.

Install it:

gem install capistrano

Then, capify your application:

cd chat-example
cap install

Edit config/deploy.rb:

-set :application, 'my_app_name'
-set :repo_url, '[email protected]:me/my_repo.git'
+set :application, 'chat-example'
+set :repo_url, 'file:///absolute/path/to/chat-example/.git'

-# set :deploy_to, '/var/www/my_app'
+set :deploy_to, "/absolute/path/to/remote-server/capwww"

Edit config/deploy/production.rb:

-role :app, %w{[email protected]}
-role :web, %w{[email protected]}
-role :db,  %w{[email protected]}
+role :app, %w{username@localhost}
+role :web, %w{username@localhost}

-server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value
+server 'localhost', user: 'username', roles: %w{web app}

-#  set :ssh_options, {
-#    keys: %w(/home/rlisowski/.ssh/id_rsa),
-#    forward_agent: false,
-#    auth_methods: %w(password)
-#  }
+set :ssh_options, {
+  keys: %w(/absolute/path/to/your/.ssh/identity-file),
+  forward_agent: false,
+  auth_methods: %w(publickey)
+}

Check your settings:

cap production deploy:check

Deploy:

cap production deploy

Verify:

ls -l /path/to/remote-server/capwww

Help

Need help with all these Capistrano tasks:

cap -T

Manually Start The Application

Manually start the application:

cd /path/to/remote-server/capwww/current
npm start

Doh! Bad luck, it does not work.

Let's create a Capistrano task to install NPM dependencies:

namespace :nodejs do

  desc "Install modules non-globally"
  task :npm_install do
    on roles(:app) do
      execute "cd #{current_path} && /usr/bin/env npm install"
    end
  end

end

Tell Capistrano to execute it after each deploy:

namespace :deploy do
  # ...

  before :restart, 'nodejs:npm_install'
end

Deploy again.

Automatically Start The Application

Let's add a task to automatically start the application:

namespace :nodejs do
  # ...

  desc 'Start app'
  task :start do
    on roles(:app) do
      execute "cd #{current_path} && /usr/bin/env node index.js &"
    end
  end

end

Start your application:

cap production nodejs:start