< Billy Overton >

Programmer / Technology Consultant

Setting up Jekyll, Gulp, and Automated Git Deployments

Posted 2015-07-27 | Billy Overton

I have been using Jekyll for a while for my personal site and long-abandoned blog, but with the latest redesign I decided to play with a more automated workflow for dependency management, development, and deployments. With a little luck that means more frequent postings, since I don’t have to go through the hassle of manually building and deploying my content.

So far I’ve settled on using a mixture of Gulp, Bower, and Jekyll for building the site and Git for deployment to my server. It’s still a work in progress that is being tweaked, but it’s finally settling down to where I felt comfortable writing about it.

Installing Global Dependencies

For my current setup there are a few global dependencies that need to be installed: node, gulp, bower, compass, and jekyll. After installing node (following the instructions from their site), the following commands install bower and gulp.

npm install -g bower
npm install -g gulp

I already had a ruby install through homebrew, so installing Jekyll and Compass is just as easy as gem install jekyll and gem install compass.

Creating the Folder Structure

Once all of the global dependencies are installed, I created the initial folder structure using Jekyll’s new command jekyll new personalsite. This creates a new folder called personalsite with the basic folder structure required by Jekyll. This is where I stopped before, using the normal jekyll build and jekyll serve commands to build and post my site.

For the new workflow, I made a few changes to the default folder structure. First I removed the css folder Jekyll made. Instead I created an assets folder with two sub folders: css and fonts. These folders are going to be generated by gulp and bower, so I also added the whole assets folder to my .gitignore file. That’s the only real change to the default folder structure.

Setting up Bower

For right now my only two bower requirements for my site are fontawesome and normalize.scss. I added those dependencies by creating a bower.json file at the root of personalsite with the following contents.

{
  "name": "personalsite",
  "version": "0.0.0",
  "authors": [
    "Billy Overton <overton.billy@gmail.com>"
  ],
  "homepage": "http://billyoverton.com",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "fontawesome": "~4.3.0",
    "normalize.scss": "~3.0.2"
  }
}

Other than some boilerplate, the main part is the list of dependencies which gives the name and the version. These dependencies can be installed by running bower install at the root of the site. This will create a bower_components folder which contains the files for each of the dependencies.

Setting up Node/NPM

To manage the Node dependencies, I created a package.json file. This mostly has the dependencies for gulp to manipulate css and integrate with browserSync and compass.

{
  "name": "BillyOvertonBlog",
  "version": "0.0.0",
  "description": "Personal Blog",
  "main": "gulpfile.js",
  "author": "Billy Overton",
  "devDependencies": {
    "browser-sync": "^2.7.12",
    "gulp": "^3.9.0",
    "gulp-bower": "0.0.10",
    "gulp-compass": "^2.1.0",
    "gulp-minify-css": "^1.2.0",
    "gulp-notify": "^2.2.0",
    "gulp-ruby-sass": "^1.0.5"
  }
}

This sets up everything so gulp can compile sass files, minify them, and place them in the assets folder so Jekyll can pick it up during the build. The dependencies can be installed with npm install from the root of the site folder.

Setting up Gulp

The most complicated part of this setup is the gulp.js file. The full file for this can be found here. Below is a breakdown of the parts.

var gulp         = require('gulp');
var cp           = require('child_process');
var minifyCss    = require('gulp-minify-css');
var notify       = require("gulp-notify")
var sass         = require('gulp-ruby-sass');
var bower        = require('gulp-bower');
var browserSync  = require('browser-sync');

The first part imports the requirements we installed with our package.json file. The only one not from the requirements file is child_process which is used later for running the Jekyll build commands.

var config = {
    sassPath: './_sass',
    bowerDir: './bower_components',
    assetDir: './assets',
    outputDir: './_site'
}
var messages = {
    jekyllBuild: '<span style="color: grey">Running:</span> $ jekyll build'
};

gulp.task('bower', function() {
    return bower()
        .pipe(gulp.dest(config.bowerDir))
});

This sets up some paths used in the later tasks, plus a message to indicate when a Jekyll build is running (for browserSync). The outputDirectory matches the build directory for Jekyll. It’s used so instead of having to trigger a build every time a file changes (like a css file), I can directly place the css into the output directory and stream the changes through browserSync.

The bower task configures the gulp-bower plugin by passing it the config.bowerDir.

gulp.task('icons', function() {
    return gulp.src(config.bowerDir + '/fontawesome/fonts/**.*')
        .pipe(gulp.dest(config.assetDir + '/fonts'))
        .pipe(gulp.dest(config.outputDir + '/assets/fonts'));
});

This is the gulp task for moving the fontawesome font files from the bower directory to both the asset directory (so jekyll will pick it up during a build) and to the output directory’s assets folder. I don’t really use the second part, but it matched what I was doing with the css task so I left it in.

gulp.task('css', function() {
    return sass(config.sassPath + '/main.scss', {
            style: 'compressed',
            loadPath: [
                config.sassPath,
                config.bowerDir + '/normalize.scss/',
                config.bowerDir + '/fontawesome/scss',
            ],
            compass: true
        })
        .pipe(minifyCss())
        .pipe(gulp.dest(config.assetDir + '/css'))
        .pipe(gulp.dest(config.outputDir + '/assets/css'))
        .pipe(browserSync.stream());
});

The css task compiles the main scss file from which I import all other files, adds the paths for the bower scss files, minifies it, outputs it to the build assets directory and the final assets directory, then triggers a browserSync stream to cause a reload.

gulp.task('jekyll-build', ['css','icons','bower'], function (done) {
    browserSync.notify(messages.jekyllBuild);
    return cp.spawn('jekyll', ['build'], {stdio: 'inherit'})
             .on('close', done);
});

gulp.task('jekyll-rebuild', ['jekyll-build'], function () {
    browserSync.reload();
});

These two tasks handle the integration between gulp and Jekyll. The jekyll-build command has dependencies on the css, icons, and bower tasks so those are completed beforehand. The first part sends a notification that there is a build occurring to browserSync. The second runs the jekyll build command.

jekyll-rebuild is the actual rebuild command for development, it depends on jekyll-build and runs a browserSync reload when the build is complete.

gulp.task('build', ['bower', 'icons', 'css' ,'jekyll-build']);
gulp.task('serve', ['build'], function() {
    browserSync.init({
        server: {
            baseDir: "./_site"
        }
    });
    // Start a watch for rebuilds
    gulp.watch(['_sass/*.scss'], ['css'])
    gulp.watch(['index.html', '_layouts/*.html', '_includes/*', '_posts/*'], ['jekyll-rebuild']);
});
gulp.task('default', ['serve']);

The rest of the file defines the entry points into the system. The build task runs the other tasks to create an initial build. This is the task I use during the deployment step on my server.

The serve task is what I use during development. In addition to running an initial build, it starts browserSync against the output directory and sets up watch commands on the files so browserSync can keep the browser up to date with any changes. Changes to the scss files triggers the css task which streams changes to browserSync. Changes to html files or post files trigger a full jekyll-rebuild, which will run a new build and reload the browser.

Configure Jekyll

Because of how Jekyll processes files, the gulp, bower, and npm files will end up being sent to the output directory _site. To avoid this, I added the following to my _config.yml file

exclude: [gulpfile.js, package.json, node_modules, bower.json, bower_components]

Using for Development and Writing

Once all that is setup, developing or writing for my site can be handled by running gulp serve. This compiles the scss files, builds the jekyll site, and loads the output in a browser window with browserSync.

If it is a fresh checkout, the gulp serve command needs to be preceded by

npm install
bower install
gulp serve

Adding the Site to Git

For my automated deployment, I went with a git work flow. The first thing I did was put the site code under revision control by running git init from the root of the personalsite folder.

Before adding and committing the first set of files, I made sure my .gitignore file ignores everything that is either auto-generated or doesn’t need to be added for the build processes.

_site
.sass-cache
node_modules
assets
bower_components
.DS_Store

Then I commited the files with a normal git workflow.

git add *
git add .gitignore
git commit -m "Initial Import"

Configuring the Server

So far all this is local to my workstation. On my remote server I initiated a bare git repository to act as the “deploy” base.

cd ~
mkdir personalsite.git
cd personalsite.git
git init --bare

To support automated building and deploying, I also installed all of the global dependencies I installed on the local machine.

Finally I added a post-receive git hook by creating the file ~/personalsite.git/hooks/post-receive and setting it as executable chmod +x ~/personalsite.git/hooks/post-receive This file will be executed every time there is a push to this repository. In my case, I use this script to create a temporary checkout, build the site, and then move it to the directory being served by my web server.

GIT_REPO=$HOME/personal-site.git
TMP_GIT_CLONE=/tmp/billyoverton_blog
PUBLIC_WWW=/var/www/billyoverton.com/

# Create a temporary checkout
git clone $GIT_REPO $TMP_GIT_CLONE
cd $TMP_GIT_CLONE

# Build the site and rsync it to the public html folder
npm install
bower install
gulp build
rsync -a --delete _site/ $PUBLIC_WWW

# Remove the temporary checkout
rm -Rf $TMP_GIT_CLONE
exit

Configuring Git Deployments

Back on my local machine, I added the repo on my server as a remote named deploy. I’m using ssh to add the remote, so there is no need for any additional server configuration beyond normal remote access (and installing git)

git remote add deploy username@remotehost.com:~/personal-site.git

With that done, the only thing required to deploy my site is to push my changes to the remote server with git push deploy master