Skip to content
Evan Summers edited this page May 7, 2016 · 101 revisions

OK, so you've heard about Node, Redis, microservices and Docker. Apparently it's like a perfect storm sweeping across the interwebs.

So let's get all futurized with this Docker containerization. Why? So we can spin up multiple instances of our stateless Node apps. Why? So we can web scale out, be resilient to failure, do continuous deployment, A/B testing, and what have you. Stateless microservices are enabled by putting our shared application state in Redis, for example.

Bin Bash

Let's create a bash script that creates a sample Dockerfile which pulls the basic Ubuntu 14.04 Docker image (about 70 megs), then installs Node and npm on top of that (40 megs).

It has been tested on an Ubuntu 14.04 droplet (Docker 1.0), AWS CentOS 6 and a CoreOS droplet. It failed on an Ubuntu 14.10 laptop (Docker 1.2), and on another Ubuntu 14.04 cloud server.

Before you sudo this script, sudo apt-get install docker.io on your Ubuntu host, or yum install docker and service docker start on your CentOS. But only try this at home on your laptop or some disposal test cloud server, and certainly not on any production servers - which could get us both in trouble. Perhaps spin up a temporary CoreOS droplet.

#!/bin/bash 

which docker || exit 1

netstat -ntlp  2>/dev/null | grep -qv 8888 || exit 1

mkdir -p ~/tmp/dockernodetest 

cd ~/tmp/dockernodetest || exit 1

pwd

echo "
  var http = require('http');
  http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello, Dockerized Node World!');
  }).listen(8888);
  console.info('test web app started');
" > index.js

echo "
  mkdir -p testapp
  cd testapp
  cp /tmp/index.js .
  nodejs index.js
" > start.sh

echo "
  FROM ubuntu:14.04
  RUN apt-get update
  RUN apt-get install -y nodejs npm
  RUN npm install http -g
  ADD start.sh /tmp/
  ADD index.js /tmp/
  RUN chmod +x /tmp/start.sh
  EXPOSE 8888
  CMD /tmp/start.sh
" > Dockerfile

ls -l 

c0_build_nodetest() {
  docker build -t nodetest .
  docker ps -a
}

c0_run_detached_nodetest1() {
  docker run -d --name nodetest1 -p 8888:8888 nodetest 
  docker ps -a
  sleep 1
  echo
  if curl -s 'http://localhost:8888' 
  then
    echo; echo
    echo "curl succeed! If on your localhost, "
    echo "try in your browser: http://localhost:8888"
    echo "or similarly with your server's IP number or name"
  else 
    echo; echo
    echo "curl failed!"
  fi
  echo "If you interrupt this script, check: docker ps -a"
  echo "and if necessary, to cleanup: docker rm -f nodetest1"
}

c0_attach_nodetest1() {
  echo "attach - press ctrl-pq to detach"
  docker attach nodetest1
  echo 
  docker ps -a
}

c0_exec_nodetest1_bash() {
  echo "exec bash - press enter to see shell, ctrl-pq to detach,"
  echo "or type exit to stop the container"
  docker exec nodetest1 /bin/bash
  echo
  docker ps -a
}

c0_rm_nodetest1() {
  if docker ps | grep -q nodetest1
  then
    echo "removing container"
    docker rm -f nodetest1
  fi
}

c0_default() {
  c0_rm_nodetest1
  c0_build_nodetest 
  c0_run_detached_nodetest1
  #c0_attach_nodetest1
  #c0_exec_nodetest1_bash
  echo "Press any key to continue, to remove container"
  read any
  c0_rm_nodetest1
}

if [ $# -gt 0 ]
then
  command=$1
  shift
  c$#_$command $@
else
  c0_default
fi

You can copy and paste the following commands to fetch the script.

mkdir -p ~/tmp
cd ~/tmp
curl -s -O https://raw.githubusercontent.com/evanx/docker-node-test/master/dockertest.sh
cat dockertest.sh
chmod +x dockertest.sh
sudo ./dockertest.sh

It fetches and runs the main script:

https://raw.githubusercontent.com/evanx/docker-node-test/master/dockertest.sh

The technicalities

Yikes, what does that all do? For starters, it creates a minimal test Node web app index.js, and a start.sh script for the container to run this app.

The test app runs on port 8888, which we publish to our host, when we run the app container. (Hopefully port 8888 is not used on your host.)

So you run the above script, and then you're like, "What all just happened?"

If you see "Hello, Dockerized Node World" then it worked! We curl'ed localhost on port 8888, which is bound to docker.io according to netstat. That there HTTP request was forwarded to our test app inside its container. Yes that's right, some kinda wonderful has just happened to your Linux computer.

Besides creating the sample index.js, start.sh and Dockerfile for you, the important commands in the above script are actually just the following, albeit as root e.g. sudo -i.

  docker build -t nodetest .
  docker run -d --name nodetest1 -p 8888:8888 nodetest 
  docker ps -a
  sleep 1
  curl -s 'http://localhost:8888'; echo
  docker rm -f nodetest1

We run the container in "detached mode" via -d, i.e. in the background, so that the script continues to the curl. If we don't detach, the script blocks there, i.e. in the console of our test Node app, which is cool, but then nothing else seems to happen.

The script waits for you to "press enter," so that you can try the URL in your browser. If you are on the host e.g. your laptop, then try http://localhost:8888 in your browser. If the host is a remote server on the cloud, then use its hostname. In this case, you might have to open port 8888 if your server is firewalled.

Finally we clean up by removing the container we created. Afterwards, you should check using docker ps if the container is still running, stopped or removed.

The gotcha's

Incidently, I found that the attach and exec commands (and --add-host option on run) didn't work for me on an Ubuntu 14.04 host (with docker -v showing 1.0), but did work on a CoreOS droplet, which has an up to date version of Docker (1.3).

As alluded to earlier, the script failed altogether on an Ubuntu 14.10 laptop (Docker 1.2), and on another Ubuntu 14.04 cloud server. However it worked on my Ubuntu 14.04 laptop, and my 14.04 cloud server.

git clone

To take this further, you'll want to deploy a Node app using git clone. In this case, include the following in your Dockerfile, to install git.

  RUN apt-get install -y git

In your start.sh, after cloning the repo and changing into its directory, you would npm install your dependencies as usual.

See: http://seanmcgary.com/posts/deploying-a-nodejs-application-using-docker

ssh key for private repo

If you are using a private repo, include the following in your Dockerfile, to add the private key to the container.

  RUN mkdir /root/.ssh
  ADD id_rsa /root/.ssh/

where you have copied id_rsa i.e. the private component of your deploy key, into the directory of your Dockerfile e.g. ~/tmp/dockernodetest in our example.

Also add the following in your start.sh script to make ssh happy.

  touch /root/.ssh/known_hosts
  chmod 700 /root/.ssh
  chmod 600 /root/.ssh/*
  ssh-keyscan github.com >> /root/.ssh/known_hosts

Then you should be able to git clone your private repo.

Danger zone

The following method is in the "danger zone" so beware. It removes all containers from your host. This is only safe if the only container is your test one! So it's not included in the above script. But you might want it, so here it is.

c0_rm_all() {
  for name in `docker ps -a | grep -v ^CONTAINER | cut -f1 -d' '`
  do 
    docker rm -f $name
  done
}

Further work

Our microservices could use Redis' pubsub and message queues to collaborate.

We might use Nginx and/or build a custom router in Node, which can handle our authentication and authorisation concerns.

We should enable centralized configuration and logging.

Enjoy and good luck!