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:
- A Digital OceanNon-affiliate 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 - 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.
$ ssh root@DIGITAL_OCEAN_DROPLET_IP
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
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.
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
.
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:
$ 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.
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: #
- 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). - 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
whenmup 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: