Run NodeJS anywhere using Docker and Nexe
A project I’m working on currently requires me to build a server side agent daemon. Due to the nature of the agent, a live connection requires to be open to the managing node server, using websockets. PHP is not very well suited to this kind of workload, so the obvious choice was to develop it using NodeJS. I will build a Docker image containing the application, as well as provide a standalone binary for people who don’t have Docker, but also don’t want the hassle of installing NodeJS.
For the purposes of this tutorial, or walk-trough if you will, we will create a very, very simple NodeJS application, which prints “Hello world from NPM”. And I will do it only with available Docker images - apart from them, there is no need to install node, npm or any other software on the server you’ll be working. All you need is Docker.
Notice: Hey there. I noticed that this post has been getting a bit of attention. If you want to update your DevOps skills with Docker and 12 Factor apps, consider purchasing 12 Factor Apps with Docker and Go. The techniques presented in the book also apply to NodeJS!
For your application, create a folder structure like this:
app/
app/package.json
app/index.js
The index.js
file should contain only one line for this test, but basically, this
is where you would write your application.
console.log("Hello world from NPM");
and the other file, package.json
, should contain something like this:
{
"name": "jstest",
"version": "0.0.1",
"scripts": {
"start": "node index.js"
}
}
The settings under scripts
enable us to run the application with a common command:
npm start
. This allows us to use any filename in the app, so we don’t need to know
about index.js
when building a Docker image.
The first thing we should do is to run the application from a Docker image. There is an official build of node on Docker Hub, and we will use it to run the application.
# docker run -it --rm --name titpetric-jstest -v `pwd`/app:/app -w /app node:0.10 npm start
> jstest@0.0.1 start /app
> node index.js
Hello world from NPM
So, just in case you didn’t know before, this is how you run a Docker image containing
NodeJS, which runs your application. Just to make things slightly easier for running
on various machines, we should still build a Docker image. In the root folder, the
one which contains the folder app
, create a file named Dockerfile
. I based mine
of a gist I found here.
FROM ubuntu:14.04
MAINTAINER Tit Petric "tit.petric@monotek.net"
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -qq update
RUN apt-get install -y nodejs npm
RUN apt-get clean
# fucking debian installs `node` as `nodejs`
RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10
VOLUME ["/app"]
ADD ./app /app
RUN cd /app && npm install
EXPOSE 8888
WORKDIR /app
CMD ["npm", "start"]
This Dockerfile
is basically a recipe, which will create a Docker image with all the
required dependencies, and will run your application on startup. Now that you have
created this file, we can build a Docker image using the following command:
# docker build -t titpetric/jstest:latest --rm .
Sending build context to Docker daemon 6.656 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> 91e54dfb1179
Step 1 : MAINTAINER Tit Petric "tit.petric@monotek.net"
---> Running in 527fe008fe3f
---> 94e1c43c8127
Removing intermediate container 527fe008fe3f
Step 2 : ENV DEBIAN_FRONTEND noninteractive
---> Running in cb97d620c92a
---> 89f31baa553b
Removing intermediate container cb97d620c92a
Step 3 : RUN apt-get -qq update
---> Running in cb96c9e58976
---> 2194977f7249
Removing intermediate container cb96c9e58976
Step 4 : RUN apt-get install -y nodejs npm
---> Running in 3d853372c7fe
[snip]
The docker build might take a few minutes, depending on how fast it will download all
the packages (node has about 100+ packages as dependencies). After the docker build
is finished, your docker image is created - you can list images using docker images
.
# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
titpetric/jstest latest 9c9bd8d9c9f1 40 seconds ago 361.8 MB
So, the image was created. Lets run it!
# docker run -it --rm titpetric/jstest
> jstest@0.0.1 start /app
> node index.js
Hello world from NPM
Success!
But, providing a 360mb docker image for a simple “Hello world” application tells us that this might not be the best use case for docker. You can package your application in this way, but it doesn’t really shine as the best example of using Docker.
Thankfully, we have an alternative: nexe
Nexe is a command-line utility that compiles your Node.js application into a single executable file.
Lets use the Docker nexe image to try and build our app:
# docker run -it --rm --name jstest-nexe-build -v `pwd`/app:/app -w /app asbjornenge/nexe-docker -i index.js -o app.bin
----> bundle /app/index.js
----> bundle -> /tmp/nexe/latest/node-v0.10.26/lib/nexe.js
----> make
[snip long text]
LINK(target) /tmp/nexe/latest/node-v0.10.26/out/Release/node: Finished
make[1]: Leaving directory `/tmp/nexe/latest/node-v0.10.26/out'
ln -fs out/Release/node node
----> cp /tmp/nexe/latest/node-v0.10.26/out/Release/node /app/app.bin
# ./app/app.bin
Hello world from NPM
Success! The application was successfully packaged, weighing in at about 11MB, which is a great deal less than the Docker image. And we have 3 different ways to run our agent application on any number of servers:
- We can install NodeJS on servers, dependencies and our application with it,
- We can install our application as a single binary built by nexe,
- We can install our application as a Docker image and run a container
Final notes - using Docker instead of a package manager is not the best thing. The Docker image for node is about 600MB. If you’re sensitive about space and run a double digit number of distinct Docker images, it’s worth keeping an eye on disk usage.
The plus side of using Docker instead of a package manager is obvious - it is incredibly clean. If you don’t use something, just wipe the container and the image, free up space instantly - and never worry if you need specific libraries for one piece of software, and other versions of the same libraries for another.
I still think it’s a bit of an elaborate way to achieve something, but when you want that something, whatever it is, to be repeatable, then it seems to be worth the effort very quickly.
While I have you here...
It would be great if you buy one of my books:
- Go with Databases
- Advent of Go Microservices
- API Foundations in Go
- 12 Factor Apps with Docker and Go
Feel free to send me an email if you want to book my time for consultancy/freelance services. I'm great at APIs, Go, Docker, VueJS and scaling services, among many other things.
Want to stay up to date with new posts?
Stay up to date with new posts about Docker, Go, JavaScript and my thoughts on Technology. I post about twice per month, and notify you when I post. You can also follow me on my Twitter if you prefer.