We code the web

Automating FTP deployment with Travis CI and Gulp

Continuous integration is an important subject, I cannot think of a development world without it. There are a lot of options for CI, but Travis is a nice and simple one. For personal projects it’s free with unlimited repo’s, perfect! However, it requires you to code your own build script. I had some trouble figuring out a good way to do FTP deployments. So, how do you upload files to FTP servers with Travis?

Travis

Travis is a Continuous Integration service that integrates with GitHub. You can sign in to Travis with your GitHub account, you can specify which repo’s it should watch. When you push new changes those repo’s, Travis gets the version of your project on their servers. It then executes the .travis.yml build script that should be in your project’s root.

Defining a build

The only way to specify build tasks is in the .travis.yml file. What can we specify? Well, for instance, we can set environment variables and run command line commands. It looks like this:

language: node_js
install:
  - npm install
  - bower install
script:
  - gulp build

We set the environment to node_js, this is typical for front-end projects as npm and build tools like gulp all run on node. Then the ‘install’ tasks are executed. Tasks are just command line commands, so they can be anything, but here we run the typical npm and bower install commands. Then the ‘script’ tasks are executed, these are the actual build tasks. In this example it is a gulp build.

Adding FTP uploads

This Travis build is nice and all, but these tasks are executed in a temporary virtual machine on Travis’ server. What would be the use of this, if the output never leaves that virtual machine? Let’s upload the result to our FTP server, so our website is updated:

env:
  global:
    - "FTP_USER=user"
    - "FTP_PASSWORD=password"
after_success:
    "curl -T index.html -u $FTP_USER:$FTP_PASSWORD ftp://wecodetheweb.com/public_html/"

This is an example from Travis’ own docs. First define some environment variables with the user name and password for accessing the FTP server. Then use good old curl to upload index.html to the FTP.

Of course, we don’t want our password in plain text on GitHub, Travis can encrypt it for us with a special command line tool. We can then replace the plain variables with the encrypted ones:

env:
  global:
    - secure: fjlZRoknWj6+UA8U65B+TZmFQv71PdsIc..
    - secure: XDdTZHvlVWMjpYgzMPKIEeRu+8namsdex..
after_success:
    "curl -T index.html -u $FTP_USER:$FTP_PASSWORD ftp://wecodetheweb.com/public_html/"

Using vinyl-ftp

This is all fine, but curl is a bit limited in options. You have to add a command for each file, or combine them with the tedious find command. No, as we allready use Gulp in this example project, why not add a gulp task for uploading the files an folders to FTP?

We need three components for this: minimist for passing command line arguments and vinyl_-ftp_ for the ftp uploading functionality and gulp-util for logging, npm install vinyl-ftp gulp-util minimist --save-dev.

First we initialize minimist to get the user name and password from the command line arguments. This way we can later on pass the encrypted username and password defined in the .travis.yml to the gulp task.

var minimist = require('minimist');
var args = minimist(process.argv.slice(2));

Now that we retrieved these arguments from the command line inside our gulp file. We can define the FTP uploading task:

var ftp = require('vinyl-ftp');
var gutil = require('gulp-util');
var minimist = require('minimist');
var args = minimist(process.argv.slice(2));

gulp.task('deploy', function() {
  var remotePath = '/public_html/';
  var conn = ftp.create({
    host: 'wecodetheweb.com',
    user: args.user,
    password: args.password,
    log: gutil.log
  });

  gulp.src(['index.html', './**/*.css'])
    .pipe(conn.newer(remotePath))
    .pipe(conn.dest(remotePath));
});

It’s that simple :). First we create new ftp connection with vinyl-ftp. Because we use gulp, we can now use glob patterns to gather the files to upload with gulp.src. Then, by piping the globs to the ftp connection, vinyl-ftp will automatically uploads all those files and creates missing folders on the server if needed. This task can now be run from the command line like: gulp deploy --user 'coolUser' --password 'mySecretPassword'.

The conn.newer(destination) method searches the destination for newer files, if there are newer files on the server then those files will not be uploaded with conn.dest(destination).

Integrating with Travis

This is the simplest part. The only thing we have to do is replace the curl command with our new gulp ftp command!

env:
  global:
    - secure: fjlZRoknWj6+UA8U65B+TZmFQv71PdsIc..
    - secure: XDdTZHvlVWMjpYgzMPKIEeRu+8namsdex..
after_success:
  gulp deploy --user $FTP_USER --password $FTP_PASSWORD

That was easy right! Setting up a Travis build can be challenging. But the result is rewarding, no more manual file copying to servers, just push to GitHub and in a few minutes your changes are live!

Reference

Related posts

Async ... await in Javascript

We’ve all been through callback hell, maybe we use Promises and Observables to get some relief. Will async await liberate us once and for all?

Better Javascript apps by optimizing state

Client side applications hold a lot of state, some view related, some data related. But how do we make sure state is reliable and managable?

Easy React Modals with Hooks and Portals

Modals, there are a thousand ways of implementing them, but the biggest challenge is to keep them simple and flexible. Let’s do that with React Hooks & Portals!