Deployment of static websites to Digital Ocean using TravisCI
Tags: digitalocean, travis, devops, staticwebsite
Published: 2019-10-11 05:06:42

*Originally posted on [Medium](https://medium.com/@tibotiber/deployment-of-static-websites-to-digital-ocean-using-travisci-957e6e0f1f9d) on May 8, 2016.*

**YMMV:** The code below is given for the environment and use-case I had at the time of writing, adapt it for your requirements.

## Stack

* Code on GitHub, master branch deploys to prod, and staging branch deploys to a subdomain for preview and QA.
* Web hosting on a Digital Ocean droplet, running Ubuntu 16.04. Using Nginx web server, serving www.planecq.com from `/var/www/planecq.com/html`.
* TravisCI for the automatic deployment when git pushing to specific branches. It pushes to a bare git repo at `/var/www/planecq.com/.git`. From there, a post-receive hook checks out the latest code to the html folder.
* Gulp used for the build and test workflow

## 1. Create an encrypted private key for travis (on your dev machine)

Install Travis CLI if needed:

```sh
gem install travis
travis login
```

Create the encrypted key and public key in your local repo:

```sh
cd ~/Sites/planecq.com
touch .travis.yml
ssh-keygen -t rsa -N "" -C "travis@planecq.com" -f travis_rsa
travis encrypt-file travis_rsa --add
rm travis_rsa
cat travis_rsa.pub # << copy this for later
```

## 2. Setup Travis on the droplet

Create a passwordless travis user on the droplet, setup the generated access key, and give it access to the folder where the website is hosted:

```sh
sudo adduser --disabled-password --gecos "" travis
sudo chown -R travis:travis /var/www/planecq.com
sudo su travis
mkdir ~/.ssh
chmod 700 .ssh
emacs .ssh/authorized_keys
"copy content of previous cat, save, exit"
chmod 600 .ssh/authorized_keys
exit
```

## 3. Prepare remote repository on the droplet

```sh
sudo su travis
cd /var/www/planecq.com
mkdir .git
cd .git
git init --bare
cd hooks
emacs post-receive
"copy the content of the hook posted below, save, exit"
chmod +x post-receive
exit
```

post-receive hook:

```sh
#!/bin/sh
git --work-tree=/var/www/planecq.com/html/ --git-dir=/var/www/planecq.com/.git checkout -f
```

## 4. Travis config

**.travis.yml**

```yml
language: node_js
node_js:
  - 4.3.1
env:
  global:
  - NOKOGIRI_USE_SYSTEM_LIBRARIES=true # speeds up installation of html-proofer
addons:
  ssh_known_hosts: webhost.planecq.xyz
branches:
  only:
  - master
  - staging
before_install:
  - rvm install 2.2.2
  - openssl aes-256-cbc -K $encrypted_b0b2958c016f_key -iv $encrypted_b0b2958c016f_iv -in .travis/travis_rsa.enc -out ~/.ssh/travis_rsa -d
  - chmod 600 ~/.ssh/travis_rsa
install:
  - gem install html-proofer
  - npm install -g gulp
  - npm install
script:
  - gulp build && gulp test
deploy:
  skip_cleanup: true
  provider: script
  script: .travis/deploy.sh
  on:
    all_branches: true
notifications:
  email: false
  slack:
    secure: rWg9[...]x69
```

**gulp build**

This is where the website is compressed for production. I have written [a separate post covering that](https://medium.com/@tibotiber/compile-and-compress-static-websites-for-production-using-gulp-8af38135b2ad).

**gulp test** (extract of gulpfile.js)

```js
var exec = require('child_process').exec;
var gulp = require('gulp');
var runSequence = require('run-sequence');
var bootlint  = require('gulp-bootlint');

// Validate html, links, etc.
gulp.task('html-proofer', function(done) {
  execute('htmlproofer ./index.min.html --check-html --check-favicon --check-external-hash', {}, done);
});

// Validate bootstrap
gulp.task('bootlint', function() {
  return gulp.src('./index.html.min')
    .pipe(bootlint({
      stoponerror: true
    }));
});

// Full test task
gulp.task('test', function(cb) {
  runSequence('html-proofer', 'bootlint', cb);
});

// Util to execute external command
function execute(cmd, opts, done) {
  console.log(cmd);
  exec(cmd, opts, function(error, stdout, stderr) {
    console.log(stdout);
    console.error(stderr);
    done(error);
  });
}
```

**.travis/deploy.sh**

```sh
#!/bin/bash

# print outputs and exit on first failure
set -xe

if [ $TRAVIS_BRANCH == "master" ] ; then

    # setup ssh agent, git config and remote
    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/travis_rsa
    git remote add deploy "travis@webhost.planecq.xyz:/var/www/planecq.com"
    git config user.name "Travis CI"
    git config user.email "travis@planecq.com"

    # commit compressed files and push it to remote
    rm -f .gitignore
    cp .travis/deployignore .gitignore
    git add .
    git status # debug
    git commit -m "Deploy compressed files"
    git push -f deploy HEAD:master

elif [ $TRAVIS_BRANCH == "staging" ] ; then

    # setup ssh agent, git config and remote
    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/travis_rsa
    git remote add deploy "travis@webhost.planecq.xyz:/var/www/planecq.xyz"
    git config user.name "Travis CI"
    git config user.email "travis@planecq.com"

    # commit compressed files and push it to remote
    rm -f .gitignore
    cp .travis/deployignore .gitignore
    git add .
    git status # debug
    git commit -m "Deploy compressed files"
    git push -f deploy HEAD:master

else

    echo "No deploy script for branch '$TRAVIS_BRANCH'"

fi
```

**.travis/deployignore**

This file replaces `.gitignore` for deploy only and lets the following files to be committed and deployed (files generated by gulp build and normally ignored in the repository):

```
gulp/
index.min.html
*.gz
```