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:
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:
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 .
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
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.
I can then access the Node.js application at http://IP_ADDRESS:8004.
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
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
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)