Automating Jekyll deploys through sFTP and Travis CI

Posted on Friday, September 4, 2020.

Return Home

I am Front End Developer that doesn't mess around with pipelines at all. I leave that stuff to the DevOps guys. However, I watched a training video on LinkedIn Learning called Learning Static Site Building with Jekyll. I wanted to use Jekyll to build this blog. Figured I get some tips. One really helpful thing about the course is that Nate talks about building an automated pipeline. Chapter 5 specifically handles the case of doing this via FTP. I learned a lot watching the course but if you're paying attention it's a little old (was published in 2018). Nate has some videos where he talks about writing a deploy script. I followed it verbatim with the exception of updating the right paths to my environment. To my dismay I encountered errors. Memory Leaks to be exact. Node warned me about this and the script failed. Being a Front End Dev I was screwed.

I didn't want to give up the dream of automating this blog. So I went on a journey. First thing I did was take a look at updated npm packages for deploying via FTP. I found this guy. I think it'll work for FTP but I need sFTP for secure connections. It definitely does not work for sFTP. Then I decided to take a look at the package Nate used in his video. It's called ssh2-sftp-client. The documentation is huge and all I wanted to do was upload a simple directory. Toughing through it though I found a method called uploadDir and some docs for it. This got me on the right track. I modified the script for my purposes and soon I was on my way.

There are 2 things you need to do get this pipeline working.

  1. Write the deploy script
  2. Setup up Travis

Writing the deploy script.

Here's my modified version of the script from ssh2-sftp-client npm docs. Make a file called deploy.js in the root of your project and copy and paste this:

'use strict';

const path = require('path');
const SftpClient = require('ssh2-sftp-client');
const remoteDir = '/remote/path/to/your/blog'; // change this to your needs

require('dotenv').config(); // load environment variables from .env file

// we don't want to commit our FTP credentials to version control so we use env variables 
const config = {
  host: process.env.FTP_DEPLOY_HOST,
  username: process.env.FTP_DEPLOY_USERNAME,
  password: process.env.FTP_DEPLOY_PASSWORD,
  port: process.env.FTP_DEPLOY_PORT || 22
};

const main = async () => {
  const client = new SftpClient();
  const src = path.join(__dirname, '_site'); // the default build directory for jekyll is '_site'. changes this to your needs however.

  try {
    await client.connect(config); // connect to the server
    await client.rmdir(remoteDir, true); // remove the old build

    client.on('upload', info => {
      // logs what's being uploaded
      console.log(`Listener: Uploaded ${info.source}`);
    });

    let result = await client.uploadDir(src, remoteDir);

    return result;
  } finally {
    // always close the connection 
    client.end();
  }
}

main()
  .then(message => console.log(message))
  .catch(error => { console.log(`main error: ${error.message}`)});

Now we need to install the packages this thing requires. Run this command:

  npm install ssh2-sftp-client dotenv --save-dev

You'll notice we installed another package called dotenv. This is for our environment variables. Make a file called .env from the root of your project and configure your environment variables like so.

FTP_DEPLOY_HOST=your host ip/domain here
FTP_DEPLOY_PORT=22
FTP_DEPLOY_USERNAME=your username here
FTP_DEPLOY_PASSWORD=your password here

Finally, we need to add a deploy script to npm. Add this to your package.json file.

"scripts": {
  "deploy": "node deploy.js"
},

That's it for the deploy script. We can now run npm run deploy from the command line to deploy our site. But it gets even sweeter. Now we need to setup Travis CI for automated GitHub deploys.

Setting up Travis CI

Travis CI is going to look for a file called .travis.yml in the root of your repo. The very first thing we need to do is create this file and commit/push it to master. Here's mine:

language: node_js
node_js:
  - 10
before_script:
  - npm install
  - bundle install
  - rm -fr _site
  - bundle exec jekyll build
script:
  - npm run deploy
branches:
  only:
    - master

Note that on line 3 I'm explicitly setting a node version. This is because without doing so Travis complained that e.isDirectory() is not a function. This error doesn't happen with at least node 10 however.

The next thing you need to do is setup an account with Travis. I suggest you use your GitHub account to do this. It's a straight forward process. Once you create an account, click the "+" icon next to the "My Repositories" tab. Select your repository then click "Settings". Under environment variables you'll need to add the same variables from you .env file. Your variables should like like this:

Environment Variables

That's it. Now every time you push to master a build will be triggered. You can see the status of the build on the "Current" tab. It should look something like this:

Build Status