Setting up a simple Continuous Deployment pipeline for Meteor

When migrating trombone to Meteor 1.3 I ran into some difficulties with my super-cheap mup to Digital Ocean deployments, and with development on Meteor Up in limbo between the new mupx and the old mup I decided to switch to a "proper" PaaS.

Of course, I looked straight to the Meteor Development Group's offering, Galaxy, which was released last year - but it turns out to be quite expensive. A single container runs to ~$25/month, and you have to host your own database (compose.io offers these from $31/month) - so running a decent sized app with some staging environments will quickly add up.

Luckily there's a new (and much cheaper) kid on the block called nodechef. It's $9 for their cheapest 128MB container, with a database bundled in, and they even handle auto-renewing Lets Encrypt SSL certificates.

I'm going to show you how to setup a Continuous Deployment pipeline, with a Staging and Production environment, automated testing and deployment for Meteor apps. We'll use codeship to build our app, github/bitbucket for storing our code, nodechef for hosting and a couple of simple bash scripts to fill in the gaps.

1. Write some tests

If you haven't already, get writing some tests. Checkout the awesome meteor guide for the basics and ensure you add a command line reporter as you'll need this in your CI build later e.g.

$ meteor add dispatch:mocha-phantomjs

2. Set up your settings.json and env.json files

For this setup, you will need two of each. One pair for Production and one for Staging - put them in separate folders:

staging  
├── env.json
└── settings.json

production  
├── env.json
└── settings.json

Nodechef uses env.json to set environment variables in your app. Use env vars to set your application url, email url and other specifics. This is what my file looks like in production (I use mailgun for emails):

{
  "PORT": "80",
  "ROOT_URL": "https://trombone.io",
  "MAIL_URL": smtp://[email protected]:[email protected]:587"
}

settings.json contains all your Meteor specific settings (read more here over at the meteor chef). During deployment nodechef will combine this with env.json into a set of environment variables, with the key METEOR_SETTINGS. I use it to store my trombone and kadira credentials, my Meteor Developer OAuth keys and some other environment variables I use in the app.

Now add production to your .gitgnore file (in the root of your app). Don't put any secrets in the staging folder if you can help it (use dummy keys where needed) and secure/backup your production secrets/folder separately from the app repository- this way you won't accidentally commit credentials to the repository.

3. Install the nodechef-cli client

Annoyingly, you'll need Java to get this going. Here's some instructions for linux but it's probably not hard on osx or windows:

  1. Download it https://nodechef.com/nodechef-cli.tar.gz
  2. Decompress it to /opt/ $ tar nodechef-cli.tar.gz xvzf -C /opt/
  3. Add it to your path $ PATH=$PATH:/opt/nodechef-cli

4. Provision your environments

The detailed instructions for this are here: https://nodechef.com/deploy - you'll want to do this once before we script things later on.

$ cd staging
$ meteor build ./ --architecture os.linux.x86_64
$ mv app-name.tar.gz app-name-staging.tar.gz
$ nodechef

enter your email, hit return, then password, hit return (there's not a lot of feedback here so it can be confusing what's going on)

$ deploy -i app-name-staging -e env.json -meteorsettings settings.json

Follow along and repeat for production. Your app-name is the name of the tar.gz file created by meteor build, we rename it because this is how nodechef names and tracks its environments.

Run your build from the codeship UI (or push to your remote git repo) and if all goes well you will now have two databases and two app instances running on nodechef! (But don't stop here, we're not done yet!)

Tip #1 You can get the URL for the instances from the nodechef web UI, or type myurl <app-name-staging/production> from the nodechef CLI

Tip #2 You can set your staging environment to 128MB RAM size to save you $$$

5. Automate Staging builds on every push to master

Now lets hook up our CI server. I'm using codeship, but you could use travis-CI, circle CI or some other provider and the instructions will roughly be the same.

  1. Setup an account, hook up your bitbucket, gitlab or github account (or other git account)
  2. Create a new project
  3. Use this as your build script to get node and meteor going:
# install node
nvm install 0.10.43  
nvm use 0.10.43  
node -v  
# install meteor
curl -o meteor_install_script.sh https://install.meteor.com/  
chmod +x meteor_install_script.sh  
sed -i "s/type sudo >\/dev\/null 2>&1/\ false /g" meteor_install_script.sh  
./meteor_install_script.sh
export PATH=$PATH:~/.meteor/  
meteor --version  

Use this for your test command (or whatever test runner/reporter you're using):

meteor test --once --driver-package dispatch:mocha-phantomjs  

and this as your deployment script:

echo Building for Staging;  
meteor npm install  
meteor build ./ --architecture os.linux.x86_64 &&  
mv <APP_NAME>.tar.gz <APP-NAME>-staging.tar.gz &&  
echo Meteor build complete, downloading nodechef-cli  
mkdir -p ./nodechef &&  
curl -s https://nodechef.com/nodechef-cli.tar.gz | tar xvz -C ./nodechef &&  
echo nodechef downloaded, deploying  
./nodechef/nodechef deploy -u YOUREMAILADDRESS -p YOURPASSWORD -i trombone-staging -e staging/env.json -meteorsettings staging/settings.json

Finally, push to your branch, wait for it to go green and check out your automated staging environment!

6. Script your Production Deployment

We want to make production deployments automated - but I prefer to keep this separate from my CI build for security reasons. Create a file called deploy.sh in your production directory containing this:

#!/bin/bash
echo Building for Production...  
meteor build ./ --architecture os.linux.x86_64 &&

echo Meteor build complete, downloading nodechef-cli...  
mkdir -p ./nodechef &&  
curl -s https://nodechef.com/nodechef-cli.tar.gz | tar xvz -C ./nodechef &&

echo Nodechef downloaded, deploying...  
./nodechef/nodechef deploy -u NODECHEF_USERNAME -p NODECHEF_PASSWORD -i APP_NAME -e env.json -meteorsettings settings.json

echo Cleaning up temp files..  
rm app-name.tar.gz &&  
rm -r nodechef

echo Production Deployment Complete!  

Make it executable:

$ chmod +x ./deploy.sh

and execute it whenever you want to deploy to production with:

$ ./deploy.sh

You could do this in your CI instead, but then you'd be handing all your private keys etc. to the CI provider and are more dependent on them to release. I prefer to just execute the deploy script when I want to update production.

7. Setup custom domains with free, automated SSL

This is super easy, follow the custom domain instructions here https://nodechef.com/deploy, (use the ROOT_URL key in your env.json files) and once logged into the nodechef cli tools, type:

deploy --ssl -i app-name -domain staging;www;@yourdomain.com  

That's it! Your custom domain will be renewed forever with Lets Encrypt every 90 days. You can also install the Meteor force-ssl package and turn on force SSL from the nodechef web UI and never worry about insecure HTTP connections again!

Oh, and the last step, don't forget to configure your trombone account ;-)