Multi-version code issues? Use Docker

TL;DR;

It can de difficult to manage support and development of multiple versions of an application through updates of major version changes as versions of the dependencies could be siginificantly different(ie, node 6 vs. node 7). Using Docker to run code directly from a container can help address the headaches of trying to manage multiple versions of an installed program.

Background

After upgrading to the lastest version of Node (8.3) some of my karma tests were failing due to a deprecated method that hadn’t been updated. I needed to run some tests in karma, but lo and behold karma isn’t well supported at that version and the following issue exists

# node --version; npm --version; karma --version; npm test
v7.0.0
3.10.8
Karma version: 1.7.0

> karma-seed@1.0.0 test /repo/learn_karma
> ./node_modules/karma/bin/karma start karma.conf.js

/repo/learn_karma/node_modules/socket.io/lib/store.js:35
Store.prototype.__proto__ = EventEmitter.prototype;
                                        ^

TypeError: Cannot read property 'prototype' of undefined
    at Object.<anonymous> (/repo/learn_karma/node_modules/socket.io/lib/store.js:35:41)
    at Module._compile (module.js:573:32)
    at Object.Module._extensions..js (module.js:582:10)
    at Module.load (module.js:490:32)
    at tryModuleLoad (module.js:449:12)
    at Function.Module._load (module.js:441:3)
    at Module.require (module.js:500:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/repo/learn_karma/node_modules/socket.io/lib/manager.js:16:13)
    at Module._compile (module.js:573:32)
npm ERR! Test failed.  See above for more details.

Nuts…

What to do? What to do?

In the olden days, before containers, custom bsh shell scripts could change PATH variables to point to the correct version. In the case of Java the script would change a “latest” sym-link to point to the current version of Java needed. Let’s see if we can do something more interesting and less invasive with containers to achieve a similar result.

Setting Up A Container

This Dockerfile (https://github.com/ericstiles/node-dev-docker/blob/master/Dockerfile) used for the node commands in this article is based on Mark Adams Dockerfile which uses chromium for headless browser testing. The key changes to this file are

  • Added karma and gulp to the $PATH variable so they can be called directly
  • Override the Dockerfile CMD to list the version for each of the following
    • node
    • npm
    • gulp
    • karma
    • chromium

This container can be used for both node development and testing. With another Dockerfile the CMD value can be overridden to run any of the commands such as node, gulp, karma on container startup in a CICD process.

The article will run through several demonstrations of running docker containers, but first the image must be built. The image is tagged to let us know that the primary node version is 6.

# docker build -t docker-chromium-xvfb:nodejs.6 --file=Dockerfile .

Running The Container

The sample Dockerfile used for building the image overrides CMD to show the versions of each application as a default. The tag for the image uses the folder structure of the project and version to clarify which version of node the solution is built on.

Flags

  • –rm: This flag removes the container when it is closed. The key here is that the docker container doesn’t take up unnecessary space and a new one is created the next time the docker run command is called.
  • -v: This flag bings a local folder (the current one in this example) to the location in the container where node looks for files. The $PWD path should be the root location of the node project where the package.json file exists. –mount is an alternative for new users.
  • -it: For interactive processes (ie, shell, use -i -t together (-it) to allocate a tty for the container process.
  • -p: Natural Address Translation between the containers ports that are available and what the host ports should point to.

Example 1: CMD

The following example runs the container CMD and will display the versions installed. This base Dockerfile is designed for development where the container will run

# docker run --rm -it -v $PWD:/usr/src/app  docker-chromium-xvfb:nodejs.6

node version:v6.10.1
npm version:3.10.10
Karma version: 0.12.37
Chromium 57.0.2987.98 Built on 8.7, running on Debian 8.7
gulp version:[15:13:12] CLI version 3.9.1

The above example demonstrates that the following commands are capable of being run from the single built image

  • node
  • npm tasks from the package.json file
  • karma directly
  • gulp task

Overriding the Dockerfile would allow for more applications to be added (eg, Bower, Yarn).

Example 2: npm and karma

With the volume tag (-v) running npm will use the package.json file from the mounted volume.

# docker run --rm -it -v $PWD:/usr/src/app  docker-chromium-xvfb:nodejs.6 npm test

> karma-seed@1.0.0 test /usr/src/app
> ./node_modules/karma/bin/karma start karma.conf.js


START:
(node:17) DeprecationWarning: process.EventEmitter is deprecated. Use require('events') instead.
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 57.0.2987 (Linux 0.0.0)]: Connected on socket jTgiVa8o3_v1171P2vrW with id 49129195
  Calculator
    + should add two numbers correctly
    + should subtract two numbers correctly
    + should add negative numbers
    + should throw an error when provided non numbers

Finished in 0.016 secs / 0.011 secs @ 19:11:33 GMT+0000 (UTC)

SUMMARY:
+ 4 tests completed

Taking the actual command for test from the package.json file karma can be run also and the same results are achieved

# docker run --rm -it -v $PWD:/usr/src/app  docker-chromium-xvfb:nodejs.6 karma start karma.conf.js

START:
(node:1) DeprecationWarning: process.EventEmitter is deprecated. Use require('events') instead.
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 57.0.2987 (Linux 0.0.0)]: Connected on socket O5olnxCgmzBQWg29-TY- with id 7704344
  Calculator
    + should add two numbers correctly
    + should subtract two numbers correctly
    + should add negative numbers
    + should throw an error when provided non numbers

Finished in 0.015 secs / 0.012 secs @ 19:44:34 GMT+0000 (UTC)

SUMMARY:
+ 4 tests completed

Example 3: nginx

A great way to run a simple web server is to run nginx in a container using teh volume tag (-v) to mount files to the container

# docker run --rm -it -p 80:80 -v $PWD:/usr/share/nginx/html nginx:latest

172.17.0.1 - - [01/Sep/2017:19:47:19 +0000] "GET /blog/multi-version-code-issues-use-docker/ HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-"
172.17.0.1 - - [01/Sep/2017:19:47:19 +0000] "GET /styles/site.css HTTP/1.1" 304 0 "http://localhost/blog/multi-version-code-issues-use-docker/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-"

Summary

The above example demonstrate a development pattern where no code is actually installed on a local computer. Containers run the build, test and server for developers. The containers start very quickly and shut down just as fast. Additional Dockerfiles can be used to expand the development environment for additional steps in the workflow.