Deploy Meteor with Docker

Levente Balogh
4 min readSep 19, 2015

--

First of all, I am using Ubuntu 14.04.

I am using Meteor JS almost for a year now, had a few hobby projects, and I love it. However, when it comes to deploying, it was always a headache for me.

As I am hosting multiple applications and sites on my server, I configured nginx as a reverse proxy and used init.d to start my meteor application. However, it was a long and complicated process to set it up for each application, and made angry when moving to an other host.

Then I started using Docker. For those who haven’t heard about, I recommend this article.

First I have tried the mup and mupx developed by Arunoda, which are both great, but for me a little too complicated to set up, and also, the MongoDB version coming with them was throwing an error on startup. However these could be fixed with some debugging, I decided to create a simple solution. I also did not want to store the root password in a file somewhere in my project.

Install Docker

$ sudo apt-get update
$ sudo apt-get install docker.io

Install MongoDB on the target server

If not already installed, you can easily install and run MongoDB on your server using docker.

We will use the docker image created by me, which uses the latest MongoDB.

$ sudo docker pull horka/mongodb
$ sudo docker run -p 27017:27017 --name mongo_instance -d horka/mongodb

This will start the mongo in daemon mode, which will be available on the port 27017.

Dockerfile for your Meteor app

Now we will create a Dockerfile for your Meteor application. In my solution, the docker image will be built on the server, and we are going to use Makefile targets to deploy.

Create a file in your project, at <project root>/deploy/Dockerfile and copy the following content into the file.
Replace “budgeter” to your app’s name everywhere.
Save it.

# deploy/Dockerfile
# Dockerfile for your app.
# Change "budgeter" in the Dockerfile to your app's name
FROM node:0.10MAINTAINER Levente Balogh <balogh.levente.hu@gmail.com>RUN apt-get install -y curl
RUN curl https://install.meteor.com/ | /bin/sh
# Change "budgeter" to your app's name
ADD . /opt/budgeter/app
# Install NPM packages
WORKDIR /opt/budgeter/app/programs/server
RUN npm install
# Set environment variables
WORKDIR /opt/budgeter/app
ENV PORT 80
ENV ROOT_URL http://127.0.0.1
ENV MONGO_URL mongodb://mongo_instance:27017/budgeter
# Expose port 80
EXPOSE 80
# Start the app
CMD node ./main.js

Add a Makefile to your project

I usually use makefile for most of my projects, because it adds easy task automation with the power of tab completion. We will only have two make targets so far.

Create a file called <project-root>/Makefile, and paste the following code
Replace the following:
<app-name>:
your app’s name everywhere.
<server-ip>: your server’s IP address.
<target-directory>: the absolute path of a directory on the server, where you want to copy the meteor bundle, e.g. /home/budgeter.
<image-name>: a name in the following format: <username>/<app-name>, e.g. horka/meteor.
<container-name>: a custom name for the docker container, e.g. meteor_budgeter.
<url>: The URL pointing to your app, e.g. http://budgeter.xyz
<port>: the port where your can be reached from outside, e.g. 8085

# Makefile
# Replace <app-name> with your app's name
# Replace <server-ip> with your server's IP address
# Replace <image-name> with custom docker tag.
# (<username>/<appname>) format
# Replace <container-name> with a custom name.
APP_NAME:=<app-name>
TARGET_DIRECTORY:=<target-directory>
SERVER_IP:=<server-ip>
IMAGE_NAME:=<image-name>
CONTAINER_NAME:=<container-name>
TARBALL_NAME:=bundle.tar.gz.
URL:=<url>
PORT:=<port>
PHONY: build
build:
@echo "-------------------------------------------------------"
@echo "Creates a tarball under ./deploy"
@echo "-------------------------------------------------------"
@echo "Building..."
# Remove previous build
@rm -rf ./bundle ./deploy/$(TARBALL_NAME)
@meteor build . --server="$(URL)" --directory
@cp ./deploy/Dockerfile ./bundle
@tar -zcf ./$(TARBALL_NAME) ./bundle
@mv ./$(TARBALL_NAME) ./deploy
@rm -rf ./bundle
@echo "Builded successfully!"
@echo "(the build output tarball is ./deploy/bundle.tar.gz)".
PHONY: deploy
deploy:
@echo "-------------------------------------------------------"
@echo "Uploading and running app in a docker container"
@echo "-------------------------------------------------------"
@ssh root@$(SERVER_IP) \
"cat > $(TARGET_DIRECTORY)/$(TARBALL_NAME) ; \
cd $(TARGET_DIRECTORY) ; \
tar -xzf ./$(TARBALL_NAME) ; \
cd ./bundle ; \
docker stop $(CONTAINER_NAME) ; \
docker rm $(CONTAINER_NAME) ; \
docker build --tag $(IMAGE_NAME) . ; \
docker run -p $(PORT):80 --name $(CONTAINER_NAME) -d $(IMAGE_NAME) ; \
" \
< ./deploy/$(TARBALL_NAME)

Usage

Go to your application’s root directory.

You can build the application using the make build command in terminal.

$ make build

You can deploy the application using the make deploy command in terminal. This will also build the application before deploying to the server.
It will ask for the root password only once. When it finished, your application will be deployed / redeployed.

$ make deploy

View in browser

Now you are able to view your application in your browser on the port you have entered to the Makefile. E.g. if you have used port 8085, then it will be <url>:8085

Multiple applications on one server

I use nginx to host multiple meteor applications on my server. For this, I have to set different port in the Makefile for each application (the port we reach the docker container from outside), and I create different site configurations for each app in nginx. Lets say you would like to use http://budgeter.xyz for your app.

Install nginx:

sudo apt-get update
sudo apt-get install nginx

Add site configuration

Create site config file:

sudo nano /etc/nginx/sites-available/budgeter.xyz

Paste the following content to the configuration file:
(you see we use port 8085, this comes from the Makefile, we choosen this in the docker run command)

# /etc/nginx/sites-available/budgetermap $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# HTTP
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

# root is irrelevant
root /usr/share/nginx/html;
index index.html index.htm;
server_name budgeter.xyz;location / {
proxy_pass http://127.0.0.1:8085;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $remote_addr;
if ($uri != '/') {
expires 30d;
}
}
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/budgeter.xyz /etc/nginx/sites-enabled/budgeter.xyz

Restart nginx

sudo service nginx restart

And the app is now available on http://budgeter.xyz!

--

--

Levente Balogh

I think one of the most important thing in our life is curiosity. Stay curious, understand truly how things work and become a master craftsman. This is my goal.