August 17, 2015

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:

A Digital Ocean Non-affiliate Droplet with nginx, as my cloud server. Chose it because of cost (had some credits), ease, and familiarity.

Droplet with nginx, as my cloud server. Chose it because of cost (had some credits), ease, and familiarity. Codeship for continuous integration - Free plan allows 100 builds per month, 5 private projects. Chose it because of cost (or lack thereof), plus ability to pull in private BitBucket repos on the free plan.

A Meteor application (referred to as my_app ), in a repository on BitBucket

), in a repository on BitBucket Meteor Up (mup) for deployment

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 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 # 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

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": [ { "host": "DIGITAL_OCEAN_DROPLET_IP", "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 .

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:

$ SETTINGS_KEY=VALUE_SET_IN_CODESHIP_ENV_VARS $ 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.

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.

If a push should not trigger a build on Codeship, append the commit message (and the merge commit message, in case of a pull request merge) with --skip-ci or [skip-ci] . By default, Codeship builds on pushes on all branches of a tracked repo - a problem if someone (like myself) likes to push frequently (and has a limited number of CI builds available in a month).

or . By default, Codeship builds on pushes on all branches of a tracked repo - a problem if someone (like myself) likes to push frequently (and has a limited number of CI builds available in a month). Ideally, I would’ve liked to setup public key crypto for encrypting/decrypting the settings file (would’ve enabled other developers on the team to encrypt without maintaining a shared secret). But with just Codeship’s SSH key in the OpenSSH format in hand, this was proving to be more difficult than it appeared initially in my head.

A failed build in Codeship can be debugged via ssh.

To remove port numbers and setup a proper URL on the cloud server, check out Setting up a proper url and removing port numbers.

Digital Ocean/Codeship gotcha: I initially set up Codeship’s public SSH key with Digital Ocean when provisioning the Droplet (there’s an option in the web interface), but somehow, that didn’t work out (led to an Error: All configured authentication methods failed when mup setup was run in Codeship), leading me to remove and rewrite the key in ~/.ssh/authorized_keys on the server.

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!

Sources:

51 Kudos