Sign inSign up

dspeed/osc-nodejs

By dspeed

Updated almost 11 years ago

Image
1

887

dspeed/osc-nodejs repository overview

osc-nodejs

A Node.JS Related Docker File for Running Node Projects

Docker from development to release

Up until recently, at my company we would manually and repeatedly build environments for development (on each developer’s machine), test and production. We began looking at using Vagrant to script the creation of Virtual Machines for applications, until our technical architect pointed us towards Docker.

Docker allows you to create lightweight, isolated and portable application environments based on Linux Container technology (rather than bulky, resource hungry VMs). We are now at a point where we can quickly spin up development environments for different application frameworks (currently NodeJS and Rails), with our Continuous Integration server building ship-able releases for other environments (e.g. test and production).

Note: Docker is very young and is currently not recommended for production environments. We have the capability to deploy a Docker container to production, but have not actually taken that step yet.

With Docker you can build and save snapshots of an environment, called images, with application dependencies ready-installed, and environment configurations ready-made. Images can be uploaded and downloaded, to and from a registry – either public at index.docker.io or privately hosted. Images can be built on the command line or by creating a Dockerfile containing a set of instructions for the Docker daemon process to follow.

To build both a development and production environment for applications we have created two “base” docker images – one containing dependencies and configuration common to all environments, and another to install common dependencies for development environments. If we look at our NodeJS setup as an example, our two base images are built with the following Dockerfiles. The images are uploaded to a private registry.

nodebase Dockerfile

# start with docker's base ubuntu image
FROM ubuntu:precise
 
# update package sources
RUN apt-get -y update
 
# NVM with default node version installed
RUN apt-get -y install git
 
RUN apt-get -y install build-essential libssl-dev curl
 
RUN git clone https://github.com/creationix/nvm.git /.nvm
 
RUN echo ". /.nvm/nvm.sh" >> /etc/bash.bashrc
 
RUN /bin/bash -c '. /.nvm/nvm.sh && nvm install v0.10.20 && nvm use v0.10.20 && nvm alias default v0.10.20 && ln -s /.nvm/v0.10.20/bin/node /usr/bin/node && ln -s /.nvm/v0.10.20/bin/npm /usr/bin/npm'
 
# forever for running node apps as daemons and automatically restarting on file changes
RUN npm install forever -g

nodedeveloper Dockerfile

# start with our nodebase image
FROM private_registry:5000/nodebase
 
# grunt init to setup new node projects
RUN npm install grunt@master -g
 
RUN npm install grunt-cli -g
 
RUN npm install grunt-init@master -g
 
RUN git clone https://github.com/gruntjs/grunt-init-node.git /.grunt-init/node
 
# install node inspector for debugging
RUN npm install node-inspector -g

At this point we can use the nodedeveloper image to build new NodeJS applications, each in their own isolated environment.

Developers can pull down the nodedeveloper image from the private registry, insert their application code in the container, install local dependencies, and run, edit and test their code. This can be done manually with the docker pull and run commands, or again by using a Dockerfile. Here is an example of a Dockerfile for a simple NodeJS app running on port 8080:

sampleapp Dockerfile

	
# start with nodedeveloper image
FROM my_registry:5000/nodedeveloper
 
# expose port 8080 for the app and another for debugging with node inspector
EXPOSE 8080
EXPOSE 8081
 
# add source code and install current dependencies
ADD . /src
RUN cd src && npm install

Once this image is built (docker build -t sampleappimage .), we can open a bash shell inside the container:

docker run -P -i -v ~/sampleapp:/src -w /src sampleappimage /bin/bash

Woah, there are quite a lot of options in that command. Here is what each one does:

-P = map all exposed ports in the container to random ports on the host.

-i = interactive mode. Docker will allow us to interact with the shell.

-v ~/sampleapp:/src = mount the sampleapp directory from the host environment inside the container. This allows us to make changes in our host environment which are “replicated” inside the Docker container.

/bin/bash = open a bash shell in the container.

With a shell open we can run the “normal” commands in the container to run, debug and test our app:

# start sampleapp.js app with support for debugging. -w to restart the app automatically when files change.
forever start -w -c "node --debug" helloworld.js
 
# run node inspector in the background on port 8081, as our app is running on the default port 8080
node-inspector --web-port 8081 &
 
# run grunt tasks to fire tests and code linting
grunt mochaTest
grunt jshint

To actually run the application from a browser in the host environment we have to find out what ports docker has mapped 8080 and 8081 to by running:
docker ps -l

Now, based on this setup, we can access localhost:xxxx and localhost:yyyy/debug?port=5858 to run and debug in the browser. It’s also possible to map exposed container ports to explicit host ports by using ‘-p from:to’ instead of the -P option on the docker run command.

We use Vincent Driessen’s excellent git workflow, so development changes are initially committed on feature branches. When changes to a branch are pushed, our CI server will use another Dockerfile to build an image that is suitable for release to another environment (depending on the branch being pushed):

sampleapp-release Dockerfile

  # start with nodebase as we don't want the developer tools
  FROM my_registry/nodebase
   
  # expose application port only
  EXPOSE 8080
   
  # add source code and install production dependencies only
  ADD . /src
  RUN cd src && npm install --production
   
  # set the working directory
  WORKDIR /src
   
  # automatically start the app when the container is run
  CMD forever sampleapp.js

Once this image is in our registry we can simply SSH onto another machine and perform a ‘docker pull’ for the image followed by a ‘docker run’ to instantly start up the application.

So far this seems to be working well, although we still have a few issues to iron out:

  • For development we have not been able to configure an IDE in the host environment to develop code in the container. We almost got there using RubyMine’s remote interpreter feature but that doesn’t support RVM. We are currently using text editors and directory mounting to edit files, and the command line to run Rake, debugging and other tasks.

  • Starting a Ruby app inside a container seems more difficult than it should be. We have not found a way to source the RVM command, and run “bundle install” inside a Dockerfile so they have to be run manually when the container is started.

  • Creating files in a directory mounted in the container, create’s protected files. They are not editable in the host without running the “chown” command.

  • If you open a shell to a container and run a blocking process like grunt watch then you can no longer interact with the container. We are looking at installing SSH in our base image and connecting over SSH as a potential solution. Another option is to run these processes as daemons and redirect any output to log files.

Thanks go out to the Docker community, which is excellent. There are countless useful articles, gists and example Dockerfiles online.

Tag summary

Content type

Image

Digest

sha256:9e0280380

Size

155 MB

Last updated

almost 11 years ago

docker pull dspeed/osc-nodejs