Running Node.js applications from GitHub in generic Docker Container image 56

Running Node.js applications from GitHub in generic Docker Container

This article shows how I create a generic Docker Container Image to run any Node.JS application based on sources for that application on GitHub. The usage of this image is shown in this picture:

 

image

Any Node.JS application in any public GitHub repo can be run using this Docker Container Image. When a container is run from this image, the url for the GitHub Repo is passed in as environment variable – as well as (optionally) the directory in the repo to run the application from, the name of the file to run and the specific version of the Node runtime to use. An example of the command line to use:

docker run -e “GIT_URL=https://github.com/lucasjellema/microservices-choreography-kubernetes-workshop-june2017” -e “APP_PORT=8080” -p 8005:8080 -e “APP_HOME=part1”  -e “APP_STARTUP=requestCounter.js”   lucasjellema/node-app-runner

This command will run the script requestCounter.js in the part1 directory in the repo found in GitHub at the URL specified. It passes an additional environment variable APP_PORT to the runtime – to be used in the node application (process.env.APP_PORT). It maps port 8080 inside the container to port 8005 on the host in the standard Docker way.

To run an entirely different Node.js application, I can use this command:

docker run -e “GIT_URL=https://github.com/lucasjellema/nodejs-serversentevents-quickstart”  -p 8010:8888 -e”PORT=8888″ -e “APP_HOME=.”  -e “APP_STARTUP=app.js”   lucasjellema/node-app-runner

The same image is started, passing a different GIT_URL and different instructions regarding the directory and the script to run – and also a different environment variable called PORT.

Note: this work is based on the Docker Image created by jakubknejzlik – see https://hub.docker.com/r/jakubknejzlik/docker-git-node-app/ and https://github.com/jakubknejzlik/docker-git-node-app/blob/master/Dockerfile.

My own sources are part of the GitHub Repository at https://github.com/lucasjellema/microservices-choreography-kubernetes-workshop-june2017 – with resources for a workshop on Microservices, Choreography, Docker, Kubernetes, Node.jS, Kafka and more.

The steps described in this article:

image

1. Docker file to build the container

2. bootstrap.sh file to run when the container is started

3. Create image from the container

4. Push image to public Docker Hub Registry (https://hub.docker.com/r/lucasjellema/node-app-runner/)

(5. Create Node.js application and push to GitHub)

6. Run Node.js application from GitHub repository by starting a Docker container from the image created in the previous steps

 

1. Docker file to build the container

The Docker file is shown here

FROM node

ENV NODE_VERSION stable
ENV NPM_SCRIPT start
ENV GIT_URL https://github.com/heroku/node-js-sample
ENV APP_PORT 3000

ENV APP_HOME .
ENV APP_STARTUP ""
# JUST_RUN specifies whether node should be installed and git should be cloned
ENV JUST_RUN N

COPY ./docker-work /code

WORKDIR /code

#RUN chown -R app:app /code/*
RUN chmod +x /code/bootstrap.sh

RUN npm install -g n --silent
RUN n stable

ENTRYPOINT ["/code/bootstrap.sh"]

It starts from the Docker Image node – the official base image (see https://hub.docker.com/_/node/ for details). The scripts defines a number of environment variables with (default) values; these values can be overwritten when a container is run. The contents of directory docker-work (off the current working directory) is copied into directory /code inside the Docker image. The file bootstrap.sh – which is in that docker-work directory – is made executable. NPM package n is installed (https://www.npmjs.com/package/n) for doing version management of Node.js and the currently stable release of Node.js is installed – in addition to the version of Node.js shipped in the Node Docker image . Finally, the entrypoint is set to bootstrap.sh – meaning that when a container is started based on the image, this file will be executed.

 

2. bootstrap.sh file to run when the container is started

The file bootstrap.sh is executed when the container is started. This file takes care of

* install a special version of the Node.js runtime if required

* cloning the Git repository – to bring the application sources into the container

* installing all required node-modules by running npm install

* running the Node.js application

The file uses a number of environment variables for these actions:

– NODE_VERSION – if a specific version of Node runtime is required

– GIT_URL – the URL to the Git repository that contains the application sources

– APP_HOME – the directory within the repository that contains package.json and the start script for the application to run

– APP_STARTUP – the file that should be executed (node $APP_STARTUP); when this parameter is not passed, the application is started with npm start – based on the start script in package.json

– JUST_RUN – when this variable has the value Y, then the container will not attempt to install a new version of Node.js nor will it clone the Git repo (again)

#!/bin/bash

if [ "$JUST_RUN" = "N" ]; then
  echo switching node to version $NODE_VERSION
  n $NODE_VERSION --quiet
fi

echo node version: `node --version`

if [ "$JUST_RUN" = "N" ]; then
  git clone $GIT_URL app
fi

cd app

cd $APP_HOME
echo Application Home: $APP_HOME

if [ "$JUST_RUN" = "N" ]; then
  if [ "$YARN_INSTALL" = "1" ]; then
    yarn install --production --silent
  else
    npm install --production --silent
  fi
fi

if [ "$APP_STARTUP" = "" ]; then
  npm run $NPM_SCRIPT
else
  node $APP_STARTUP
fi

 

3. Build container image

In my environment, I am working on a Windows7 laptop. On this laptop, I have installed Docker Tools. I am running in the Docker Tools Quickstart terminal (fka boot2docker) – Docker Machine on a Linux client running a small Oracle VirtualBox VM.

Using this command I build the container image from the Dockerfile:

docker build -t lucasjellema/node-app-runner .

SNAGHTMLb25b94a

To inspect whether the image contains the setup, I can run the image and start a Bash shell – just check on the contents of the file system:

docker run  -it –entrypoint /bin/bash  lucasjellema/node-app-runner

I can now try out the image, using a command like this:

docker run -e “GIT_URL=https://github.com/lucasjellema/microservices-choreography-kubernetes-workshop-june2017″ -e “APP_PORT=8080” -p 8004:8080 -e “APP_HOME=part1”  -e “APP_STARTUP=requestCounter.js”   lucasjellema/node-app-runner

image

This runs a container, clones the Git repo at the indicated URL to directory /code/app , navigate into directory /code/app/part1, performs an npm install to get required modules and runs requestCounter.js with Node.js, listening at port 8004 for http requests on the host that are forwarded to port 8080 inside the container.

In order to access the application on my Windows host, I need to know the IP address of Docker Machine – the Linux VM instance that runs the Docker server inside VirtualBox. This is done using

docker-machine ip default

which will return the IP address assigned to the VM.

image

I can then access the Node.js application at http://IP_ADDRESS:8004.

 

image

 

4. (optional) Push image to public Docker Hub Registry (https://hub.docker.com/r/lucasjellema/node-app-runner/)

The image has proven itself, and we can now push it to a public or private registry. To push to Docker Hub:

docker login

docker push lucasjellema/node-app-runner

image

5. Create Node.js application and push to GitHub

image

6. Run Node.js application from GitHub repository by starting a Docker container from the image created in the previous steps

I have several Node.js applications that I would like to run – each in their own container, listening at their own port. This is now very simple and straightforward – using several calls to docker run, each with different values for GIT_URL, APP_HOME and APP_STARTUP as well as APP_PORT or PORT.

For example – run three containers in parallel:

docker run -e “GIT_URL=https://github.com/lucasjellema/microservices-choreography-kubernetes-workshop-june2017″ -e “APP_PORT=8080” -p 8001:8080 -e “APP_HOME=part1”  -e “APP_STARTUP=requestCounter.js”   lucasjellema/node-app-runner

docker run -e “GIT_URL=https://github.com/lucasjellema/microservices-choreography-kubernetes-workshop-june2017″ -e “APP_PORT=8080” -p 8005:8080 -e “APP_HOME=part1”  -e “APP_STARTUP=requestCounter-2.js”   lucasjellema/node-app-runner

docker run -e “GIT_URL=https://github.com/lucasjellema/nodejs-serversentevents-quickstart”  -p 8010:8888 -e”PORT=8888″ -e “APP_HOME=.”  -e “APP_STARTUP=app.js”   lucasjellema/node-app-runner

We can look at the logging from a container:

docker logs <container id>

We can stop each container:

docker stop <container id>

list all containers – running and stopped:

docker container ls -all

restart a container (now the time to restart is very short):

docker start <container id>

7. Turn Container into Image

Note: it is easy to turn one of these containers running a specific Node.js application itself into an image from which subsequent containers can be run. This image would contain the correct version of Node.js as well as the application and all its dependent modules – allowing for a faster startup time. The steps:

docker commit CONTAINER_ID NAME_OF_IMAGE

for example:

docker commit a771 request-counter

Subsequently we can run a container based on this image; note that this time we do not specify the GIT_URL – because the application and all node_modules are baked into the image. The environment variables used in bootstrap.sh and in the application can still be passed. The startup time for this container should be very short – since hardly any preparation needs to be performed:

docker run  -e “APP_PORT=8080” -p 8004:8080 -e “APP_HOME=part1” -e “JUST_RUN=Y” -e “APP_STARTUP=requestCounter.js”   request-counter

 

Notes

Note: remove old containers

list exited containers:

docker ps -aq -f status=exited

remove them (http://blog.yohanliyanage.com/2015/05/docker-clean-up-after-yourself/)

docker rm -v $(docker ps -a -q -f status=exited)

remove dangling images

list them:

docker images -f “dangling=true” -q

Remove them:

docker rmi $(docker images -f “dangling=true” -q)