As of the last year or so, I have transitioned into using GIT for not only backing up my sites onto a remote server but its also a wonderful tool for keeping track of past changes, working on separate branches for time sensative releases, and even deploying updates to the server.

My CMS of choice for the last 4 or so years has been ExpressionEngine and there are a number of intricacies when dealing with a cms. For one, the cms at times updates files that are on the server, so you don't want your deploy to mess with those files, at least if you intend on keeping that smile on your clients face.

This is the logic behind my workflow with GIT and ExpressionEngine...

First off, my favorite editor is SublimeText, the best editor on the planet. It comes with the ability to install lots of add-ons to make sitting at a desk all day, that much better (hopefully). One add-on that I wouldn't leave home without is GIT Gutter, it integrates with git on your local machine to show you in the gutter of your edited files, what lines have been modified since your last commit. It's a quick way of keeping track all of your updates. Another is 'sftp', it makes ftp'ing files a breeze. It adds a sftp config file to your main directory where you can easily put in your ftp credentials and settings. 

Whenever I'm building an EE website, I always create a local git repository and I create a backup on my remote server (for safe keeping). If I have ssh access to the clients server, then its a no brainer, I setup git and work at getting my deployment method rolling. Git has what is called Hooks, these are nifty little code chunks, that you can write, that will fire when a certain event happens within a git repo. There is a 'post-receive' hook that is of particular interest to you. This hook runs after you have pushed an update to the remote repository. This is the time when we want our code to modify the server with an update of our latest changes. You can even have the hook look at what branch was updated so you can determine what options you want to take. I use my master branch to checkout files to my production directory and a staging branch to checkout to my staging directory.

When developing and maintaining an EE website for a client, I like to have a local version (for making and viewing updates quickly), a staging version that only the client has access to, for showcasing updates and getting them approved, and obviously the live production version that everyone sees. This means you need 3 locations for the files, 3 databases and a modified database.php file to store the different database settings for each server. It's also nice to be able to keep them all in sync as quickly as possible, which isn't the easiest thing to do when dealing with a CMS, however, I believe I have a fairly simple way to keep them in sync. It's a combination of GIT (I actually prefer GIT Tower over the command line), ftp, Sequel Pro (or any decent mysql client), and an EE module called REElocate.

I use GIT for updating any files I modify such as css, javascript, html templates, added add-ons, config/database settings, etc. I use sftp in Sublime Text to quickly sync the upload directory on the live server down to my local server, so I can grab the latest updated files from the CMS. I can then upload/sync that directory back up to the staging server, if I need to. Sequel Pro is a quick way to export the live database and import it into my other environments and finally REElocate is used to update all of the directory path and domain settings in EE, so it works properly at each location. It usually takes no more than a couple of minutes to be all synced up. I generally try to have a staging.website.com subdomain for the staging environment and a website.dev domain for my local environment.

ExpressionEngine Configuration

Now for the exciting part. In config.php I determine what environment I am in and define a variable and add a config variable that I can use in templates. Here is the code...

if(!defined('SERVER_ENV')) {
    // Define the server name, basepath and site_url
    // These can all be defined using the server request and filepath
    define('SERVER_NAME', $_SERVER['SERVER_NAME']);

    if ( strstr( SERVER_NAME, '.dev' ) ) define('SERVER_ENV', 'local');
    elseif( strstr( SERVER_NAME, 'staging.' ) ) define('SERVER_ENV', 'staging');
    // Fallback to production settings
    else define('SERVER_ENV', 'production'); // live

    // Not sure what environment the request is?
    // Add ?debug_config_bootstrap to the end of the URL
    if(isset($_GET['debug_config_bootstrap'])) {
        die('The current environment is: '.SERVER_ENV);
    }
}

global $assign_to_config;
if(!isset($assign_to_config['global_vars']))
    $assign_to_config['global_vars'] = array();
$assign_to_config['global_vars']['environment'] = SERVER_ENV;

Notice the last line I set a global variable called 'environment'. I can now use {environment} in any template I want. I use it for displaying a bar at the top of the page (when not on production), of what environment I am viewing. Here's what I add to my EE template...

[if environment != 'production']<p class="dev-bar">[environment] Environment</p>[/if]

* replace the square brackets with curly brackets

Then you just style it however you want. The main part of this is in the database.php file. We can use our newly defined environment variable in a conditional to determine our different environments. 

switch ( SERVER_ENV ) {

/* Local DB Config */
case 'local' :
$db['expressionengine']['hostname'] = 'localhost';
$db['expressionengine']['username'] = 'username_here';
$db['expressionengine']['password'] = 'password_here';
$db['expressionengine']['database'] = 'db_name';
$db['expressionengine']['cachedir'] = '/path/to/expressionengine/cache/db_cache/';
break;

/* Development DB Config */
case 'staging' :
$db['expressionengine']['hostname'] = 'localhost';
$db['expressionengine']['username'] = 'username_here';
$db['expressionengine']['password'] = 'password_here';
$db['expressionengine']['database'] = 'db_name';
$db['expressionengine']['cachedir'] = '/path/to/expressionengine/cache/db_cache/';
break;

/* Development DB Config */
case 'production' :
$db['expressionengine']['hostname'] = 'localhost';
$db['expressionengine']['username'] = 'username_here';
$db['expressionengine']['password'] = 'password_here';
$db['expressionengine']['database'] = 'db_name';
$db['expressionengine']['cachedir'] = '/path/to/expressionengine/cache/db_cache/';
break;

}

Git Deployment

ExpressionEngine is all setup. Now lets get git working. Assuming you already have a remote repo on the production server. You need to add/edit ssh into the server and in your git/hooks directory, you should see a file called post-receive. Open that in nano (or whatever you prefer). Then you add this bash script to the file.                                                                                   

#!/bin/bash

# To use this functionality turn this to true
enabled=true

###### SETTINGS ######

# set the server paths here
livepath=/live/path/here
devpath=/dev/path/here/

#set the user
git_user=ftp_username
git_group=ftp_usergroup

#file permissions
#666
sixes=(
        'system_dir/expressionengine/config/config.php'
        'system_dir/expressionengine/config/database.php'
      )
#777
sevens=(
        'system_dir/expressionengine/cache/'
        'images/avatars/uploads/'
        'images/captchas/'
        'images/member_photos/'
        'images/pm_attachments/'
        'images/signature_attachments/'
        'images/uploads/'
      )

#files/directories to remove
#used for if you want to git backup directories but don't want them in your htdocs location
removedirs=(
            '_db'
            'sftp-config.json'
            '.gitignore'
          )

###### LOGIC ######

# this handles the updates
if [ "$enabled" = true ]; then

  while read oldrev newrev ref
    do
      branch=`echo $ref | cut -d/ -f3`

      if [ "master" == "$branch" ]; then
        deploydir="$livepath"
        msg="Changes pushed live."
      fi

      if [ "staging" == "$branch" ]; then
        deploydir="$devpath"
        msg="Changes pushed to dev."
      fi

      # update the directory
      git --work-tree="$deploydir" checkout -f $branch

      # fix owner
      FILES=`git diff --cached --diff-filter=ACMRTUXB --name-only ${oldrev}`
      for F in $FILES; do
        chown $git_user:$git_group "${livepath}${F}"
        echo "chowned ${livepath}${F}"
      done

      #set file permissions
      for F in "${sevens[@]}"; do
        chmod 777 "${livepath}${F}"
      done
      for F in "${sixes[@]}"; do
        chmod 666 "${livepath}${F}"
      done

      #remove files/directories
      for D in "${removedirs[@]}"; do
        rm -rf "${livepath}${D}"
      done

      echo "$msg"
  done

fi

The comments make what it does self explanitory. Git does a checkout to a desired working tree and that directory is determined by the name of the branch that is being pushed to the server. In my case, It then sets the desired user as the owner of those files, then it sets the necessary file permissions that we need. I wanted to backup certain files/directories but I didn't necessarily want them accessible in the production directory. So I have a list of files that it removes after the checkout occurs.

Finally, I also have a gitignore file in the root directory of my files, that prevents git from managing or overriding certain files.

# Track all .gitignore files
!.gitignore # or !.gitkeep if you prefer

# Ignore all cache & private files
/system_dir/expressionengine/cache/
/images/sized/
/images/uploads/

# Ignore EE file upload directories
/assets/

# Ignore design directories
/_layout/
/_inc/

That's about it. Once that is all setup, managing all the different environments is fairly simple. With every update you make and push to the Git repo, the files you want updated on your server will be updated and only if you push to the master or staging branch. I'm sure this may evolve over time but, so far I like it.

EDIT: you may also want to see this article I wrote about integrating htaccess/htpasswd files with multiple environements.

« Back to Main