Deploying a Meteor application to Digital Ocean using Codeship and Meteor Up (mup)

I’ve been working on a Meteor project that just reached a somewhat deployable state – figured it would be fun to experiment with and document the deployment process.

Before proceeding, do note that this is not a tutorial or a follow-to-the-letter guide. I simply saw this post as an opportunity to document how the different pieces fit together (something I tried to look up when I embarked on the deployment task).

I chose to go with the following pieces of technology:

Setting Up Digital Ocean #

Create a droplet with the LEMP application image (comes with nginx pre-installed). Alternatively, select the Ubuntu 14.04 x64, and install nginx manually.

I chose the $5 server - enough for my needs at the moment, since this deployment is not truly “production” for me.

Use the emailed credentials (no need, if ssh keys are set up) to remotely ssh into the server.


Update the distro:

$ sudo apt-get update

Check if nginx is installed:

$ nginx -v
nginx version: nginx/1.4.6 (Ubuntu)

If it is not:

sudo apt-get install nginx

To start, stop or restart nginx, use the following:

sudo service nginx stop
sudo service nginx start
sudo service nginx restart

I’ll get back to the cloud server in a bit.

Setting up Codeship #

Create a new project by adding my_app from it’s BitBucket repository

In the my_app project, go to Project Settings:

Setup and Test #

Click on Test Settings

For the Setup Commands, choose I want to create my own custom commands, and add the following:

# Node Installation
nvm install 0.12
nvm use 0.12

# Meteor Installation
curl -o
chmod +x
sed -i "s/type sudo >\/dev\/null 2>&1/\ false /g"
export PATH=$PATH:~/.meteor/
meteor --version

# NPM Package Installs
npm install -g mup

This installs Node and Meteor on the CI server, as well as any necessary NPM modules.

Under Configure Test Pipelines, leave the Test Commands section untouched. Later on, this can potentially be used to run linting (e.g. jshint) and tests against the project.

Save Changes

Deploy #

Click on Deployment Settings

Click on Add a new deployment pipline and add the branch name which will trigger deployment. my_app uses the master branch for deployment, meaning that any pushes to master will trigger the deploy step on Codeship.

Select Custom Script

The deploy scripts are run in the project directory. Add the following:

# Enter deploy directory
cd .deploy

# Decrypt Settings file
openssl enc -aes-256-cbc -d -in settings.json.enc -out settings.json -k $SETTINGS_KEY

# Run mup
mup setup
mup deploy

This enters the deploy directory, decrypts the settings file, and runs setup and deploy according to the provided mup.json file (see next)

Save Changes

Setting up CI Environment Variables #

Click on Environment Settings

Add a new environment variable, with key: SETTINGS_KEY, and set the value to a randomly generated passkey (later used to encrypt settings.json).

Save Changes

Adding Codeship SSH Public Key to Digital Ocean Cloud Server #

In the my_app Project Settings in Codeship, click on General Settings.

Generate an SSH key pair if one does not exist

Copy the public key

In the ssh session into the Digital Ocean cloud server (from the first section), append the Codeship public key to the file ~/.ssh/authorized_keys.

Setting up mup #

On the local dev machine, install the Meteor Up tool (mup):

$ npm install -g mup

Go to the my_app project directory, create a new directory: .deploy.

$ cd /some/path/to/my_app
$ mkdir .deploy
$ cd .deploy

Inside .deploy, run:

$ mup init

This will generate two files: mup.json and settings.json

Replace the contents of mup.json with the following:

  "servers": [
      "username": "root",
      "pem": "~/.ssh/id_rsa"
  "setupMongo": true,
  "setupNode": true,
  "nodeVersion": "0.10.36",
  "setupPhantom": true,
  "enableUploadProgressBar": true,
  "appName": "my_app",
  "app": "../",

  "env": {
    "ROOT_URL": "http://localhost",
    "PORT": 3000,
    "METEOR_ENV": "production"
  "deployCheckWaitTime": 15

Check out the commented version of mup.json to understand the meaning behind each key/value pair.

Most of the file is in its default state. Add the Digital Ocean Droplet IP, and remove the password field, and uncomment the pem field - enabling the use of an ssh key instead of a password to deploy to the cloud server. This way, the mup.json file can be committed and pushed to source control - it contains no secret information.

Run the following:

$ mup setup
$ mup deploy

If all goes well, the application is now deployed at DIGITAL_OCEAN_DROPLET_IP:3000.

Encrypting settings.json #

The my_app projects needs to store (secret) API keys in the settings.json file - hence, this file cannot be committed (in its unencrypted form) to source control. A potential solution would’ve been to switch to using environment variables, and set them when provisioning the Droplet. My solution for this was to commit and push an encrypted version of the settings file used by mup deploy (which will then be decrypted and used by Codeship at the deployment step).

To encrypt the file, run the following:

$ openssl enc -aes-256-cbc -salt -in settings.json -out settings.json.enc -k $SETTINGS_KEY

The encrypted settings.json.enc can be committed and pushed to source control.

Conclusion #

If all goes well, the push should trigger a build and deploy process on Codeship - which, if successful, will deploy a fresh build of the Meteor app on the Digital Ocean cloud server.

Random Notes: #

I will try to update and improve this blog post over the next couple of weeks for content and clarity, as I think of other things that I may have done (or need to do for any issues that crop up).

Questions? Feedback? Feel free to reach out to me on Twitter!



Now read this

RoadWatch wins Canon Canada’s Through Your Lens Competition

For the past couple of months, my former teammate from the NASA Space Apps Challenge, @femion, and I have been working on RoadWatch, a project for the Canon: Through Your Lens competition. Along with six other finalists, we pitched to... Continue →