DevOps, Git, gulp.js

08 Mar 2016

I really like Heroku’s deployment based on Git, but for my basic hosting needs I’m quite satisfied with a simpler web host. Can we deploy to a plain server with Git, including automatic building on the server using gulp.js?

The Problem

If you have worked with Git and gulp.js, I don’t need to explain why using the two in conjunction to deploy web apps to a production server is great. Wouldn’t it be nice if all you had to do was to run

$ git push production

from your local project repository, and your application would update and build on the server?

Prerequisites

We’re going to use Webfaction for this tutorial. They’re a pretty affordable web host, but allow you to easily set up Git repositories that you can push to.

This tutorial assumes that you have Node.js, Bower and npm installed on both the server and your local machine, as well as basic familiarity with Git and gulp.js. We’re also going to use Foundation for Sites to very quickly set up a simple web project that is built and optimized for production using gulp.js.

Obviously, in the example commands below, username will have to be replaced with your actual username on the server.

Server Setup

Using Webfaction’s control panel, I set up a fresh website. I chose a static application because our website is only going to consist of a Hello World page. My application is called git_deploy_tutorial.

I then set up a Git remote through the same control panel. I called it git_deploy_tutorial_remote.

After sshing into my server, I can confirm that the two folders were created under my home directory:

$ ssh username@username.webfactional.com

$ ls ~/webapps/git_deploy_tutorial
index.html

$ ls ~/webapps/git_deploy_tutorial_remote/repos/proj.git/
branches  config  description  HEAD  hooks  info  objects  refs

Very nice. You can see in the listing for our Git folder that Webfaction created a bare repository for me.

Note

If you are already familiar with using gulp.js to build websites for production, and you’re mostly interested in automating the build with Git, you may want to skip ahead to Creating a Post-Receive Hook.

Setting up the Project

Back on our local machine, we’re going to set up a fresh web project using Foundation’s command-line tool. In case you are not using Foundation, what’s happening here is basically this: a website project is set up from a template. It will use Bower for frontend dependencies. Stylesheets are going to be written in Sass, so it uses npm and gulp.js to preprocess the scss files and turn them into css files that the web browser can understand.

So let’s quickly set up the project using the Basic Template:

$ foundation new
? What are you building today? A website (Foundation for Sites)
? What's the project called? (no spaces) git_deploy_tutorial
? Which template would you like to use? Basic Template: includes a Sass compiler

This will take a while to install all the dependencies. When it is done, we can cd into our project folder and initialize a Git repository:

$ cd git_deploy_tutorial
$ git init
$ git add .
$ git commit -m "Initial commit!"

Alright! We’re almost ready to try pushing to the Git remote we created earlier. Let’s add the remote and call it production:

$ git remote add production username@username.webfactional.com:/home/username/webapps/git_deploy_tutorial_remote/repos/proj.git

Now let’s push our initial commit to the remote:

$ git push -u production master

This should work without an error and tell you something like “branch master set up to track remote branch master from production.”

We can verify that our local commit made it to the remote by sshing back into the server, and checking the repository’s log:

$ ssh username@username.webfactional.com
$ cd /home/username/webapps/git_deploy_tutorial_remote/repos/proj.git
$ git log

This should show you the commit you made locally earlier. Great! Let’s move back to our local project and work on the build script.

Modifying the build script

Let’s take a look at our local project folder. As mentioned above, frontend dependencies are managed using Bower. To install all dependencies listed in bower.json, we have to run the following command:

$ bower install

This should have downloaded and installed all frontend dependencies inside bower_components/. Foundation has also set up a gulpfile.js for us, which contains automation tasks. By default, it watches for changes in any of the scss files, upon which it compiles the css files. We can start it using npm start – but first, let’s add some build tasks. We would like to:

  • Serve the files locally during development so that we can inspect our changes, for instance with gulp-serve
  • Compress the CSS output in production, using gulp-cssnano.

We’ll use npm to install the new dependencies:

npm install --save-dev gulp-serve gulp-cssnano

Then let’s add gulp to the scripts section in package.json so we’ll be able to run our own tasks:

// in package.json
"scripts": {
  "start": "gulp",
  "gulp": "gulp", // <== add this line
  "build": "gulp sass"
},

Now let’s add the webserver for development. In gulpfile.js, add the following tasks:

var serve = require('gulp-serve');
// ...
gulp.task('watch', ['sass'], function() {
  gulp.watch(['scss/**/*.scss'], ['sass']);
});

gulp.task('serve-dev', ['watch'], serve({
  root: ['.'],
  port: 8080
}));

Great. Now we have a task called serve-dev, which will also watch for any changes in the scss files. Let’s start it up:

$ npm run gulp -- serve-dev

Now use the web browser of your choice, and open http://localhost:8080. You should see the file index.html correctly rendered in all its glory – Foundation’s Welcome page showing off all of their components.

To see how your CSS files are automatically rebuilt, just open scss/_settings.scss and change any of the settings, for instance:

$header-color: red;

Then refresh the page in your browser and watch how your change immediately came into effect. But let’s say good-bye to our dev server for now (quit it with Ctrl-C) and move on to the final point of this tutorial: deployment and building on the server. Just don’t forget to commit your changes:

$ git commit -am "Added dev server task (and made all headlines red)"

We’re almost there.

Adding the production build task

Leaving more complex things like auto-prefixing aside for now, we definitely want to minify our CSS output. We’ve installed everything we need earlier, and now we simply add the following task, called build-dist, to our gulpfile:

var cssnano = require('gulp-cssnano');
// ...
gulp.task('build-dist', function() {
  return gulp.src('scss/*.scss')
    .pipe($.sass({
      includePaths: sassPaths
    })
      .on('error', $.sass.logError))
    .pipe(cssnano())
    .pipe(gulp.dest('css'));
});

You can test that it worked by running it and looking at how the resulting CSS is wonderfully garbled:

$ npm run gulp -- serve-dev
$ cat css/app.css

Creating a Post-Receive Hook

Back to our production server. The heavy lifting is done. Now all comes together with the help of a Git post-receive hook. Basically this is just a shell script that will be executed each time you push to the remote on the production server.

Let’s create the hook:

$ ssh username@username.webfactional.com
$ cd ~/webapps/git_deploy_tutorial_remote/repos/proj.git/
$ nano hooks/post-receive

In the text editor that opens, we’ll write down the steps that build our application.

#!/bin/bash

# make sure you have the Node.js binaries on your path --
# namely `npm` and `bower`
PATH=/home/username/path/to/nodejs/bin:$PATH

# update the web application with the contents of our repository.
# WARNING: this might destroy changes you might have made inside
# webapps/git_deploy_tutorial and overwrite them with your 
# Git repository's changes.
GIT_WORK_TREE=/home/username/webapps/git_deploy_tutorial git checkout -f

# install missing dependencies
cd /home/username/webapps/git_deploy_tutorial
npm install
bower install

# run your production build script
npm run gulp -- build-dist

Exit the text editor saving the file. Now all that’s left is making our hook executable:

$ chmod +x hooks/post-receive

Let’s Try it Out!

Back on our local machine. Time has gone by. Our project has seen progress. We are ready to deploy. From your local repository folder, type the magical deployment command:

$ git push production

Git pushes your changes to the production remote and, after receiving all of them, executes your post-receive hook. Since the css folder is listed in your repository’s .gitignore file, the compiled CSS files are not transferred when pushing. Instead, the hook causes them to be built remotely on the server, as you can see in the console output when pushing.

Assuming you’ve not received any error messages, go ahead and check out your website in your browser. Congratulations, there’s your updated Welcome page – with red headers no less.