This is a quick post for getting up and running with io.js and a single, simple Dockerfile using Docker 1.6, which was just released. It is not an exhaustive post on using either io.js or Docker.
Install Docker
Go get Docker and install it from here.
Verify that Docker 1.6 is running:
1 2 |
$ docker -v Docker version 1.6.0, build 4749651 |
Get the iojs image
Now go get the official iojs image:
1 |
$ docker pull iojs |
This will download the latest iojs image from the official iojs repo on Docker Hub.
An image is used by Docker to launch a process in a container. An image is comprised of all the bits that provide an operating system environment for running a process. When the process runs, it runs in a “container” completely isolated from any other process as if it were running in its own machine.
You can verify that the image has been downloaded successfully by entering:
1 2 3 |
$ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE iojs latest 90f5055d3cce 9 days ago 700.6 MB |
You can verify that no containers are currently running by entering:
1 2 |
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
(Note that the column header names wrap, and so do any container entries, based on the width of your terminal)
If you have already been using Docker, you may see a few containers listed, but you shouldn’t see any with the image name ‘iojs’ yet.
Run a container
The iojs image was built to run the iojs command if you don’t explicitly specify a different command to run. Like node, the iojs command creates a REPL for entering JavaScript commands
You can test it like this:
1 2 |
$ docker run --rm -it iojs > |
The REPL just waits for you to enter JavaScript. You can enter <Ctrl-c>
twice to exit the REPL.
What did we do? We told docker to start a container based on the iojs image. The default command for the iojs image is iojs
, so it started a REPL and waited for JavaScript to be entered.
The reason it waited is because of the -it
options. The i
option keeps STDIN open for the process, and the t
option allocates a pseudo-TTY. The combination is what allows us to interact with the REPL as if we had started it directly in a terminal instead of inside of a container.
When trying things out, you will create a lot of containers. It’s a good idea to use the -rm
option to automatically delete the container when you stop it to keep things tidy.
Override the default command
When we tell Docker to start a container from the iojs image, we can explicitly provide a command to override the default one. The following will run bash instead of
1 2 |
$ docker run --rm -it iojs bash root@2ee47e2148ec:/# |
This will put you at a bash prompt in the container. You can check the version of iojs you’re running:
1 2 |
# iojs -v v1.6.4 |
Since node is symlinked to iojs when iojs is installed, you can also just enter:
1 2 |
# node -v v1.6.4 |
As you can see, the current iojs image runs iojs v.1.6.4.
The docker-iojs team tracks the latest io.js versions and updates the official iojs image for Docker as quickly as they can.
The io.js team releases new versions fairly rapidly and the newest version is now 1.7.1. The docker-iojs team supports three different iojs images (explained later). They move pretty quickly to update the iojs Dockerfiles (used to build images) with a pull request. They then submit yet another pull request with the information necessary to update the official repo and make the new images publicly available. The latest version (1.7.1) should be available within the next day or so.
Using the iojs:onbuild
image
The iojs image we used above is not the only image the docker-iojs team maintains. They also maintain a slim
version (not recommended except under specific circumstances) and an onbuild
version that makes building derivative images easier.
The onbuild
version is based on the plain iojs
version used above, but it copies your node application to the container and then runs its. Creating a derivative of this image can be as simple as referencing it and specifying the port you want to expose.
Here’s a simple example to illustrate. Create a demo directory.
Create a package.json
file with the following:
1 2 3 4 5 6 7 |
{ "name": "simple-docker-iojs-demo", "version": "1.0.0", "scripts": { "start": "node app.js" } } |
Create app.js
and just print something to the console to prove it works:
1 |
console.log('hello world!'); |
Create a Dockerfile
:
1 |
FROM iojs:onbuild |
Now create an image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ docker build -t demo-app . Sending build context to Docker daemon 4.096 kB Sending build context to Docker daemon Step 0 : FROM iojs:onbuild # Executing 3 build triggers Trigger 0, COPY package.json /usr/src/app/ Step 0 : COPY package.json /usr/src/app/ ---> Using cache Trigger 1, RUN npm install Step 0 : RUN npm install ---> Using cache Trigger 2, COPY . /usr/src/app Step 0 : COPY . /usr/src/app ---> Using cache ---> 9aa71d87d65d Successfully built 9aa71d87d65d |
Docker will create an image based on the Dockerfile in the current directory and name it demo-app
. As part of creating the image, it ran instructions from the onbuild
base image that we specified in our Dockerfile that included copying the contents of the current directory to /usr/src/app/
and running npm install
.
You can see what the instructions look like here.
Now run demo-app
in a container:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ docker run --rm demo-app npm info it worked if it ends with ok npm info using npm@2.7.5 npm info using node@v1.6.4 npm info prestart simple-docker-iojs-demo@1.0.0 npm info start simple-docker-iojs-demo@1.0.0 > simple-docker-iojs-demo@1.0.0 start /usr/src/app > node app.js hello world! npm info poststart simple-docker-iojs-demo@1.0.0 npm info ok |
The iojs app is trivial, but the mechanics of how this works is the same for more complicated examples. One thing you will probably want to do is export a port for accessing your node application, which we cover in the next section.
Pushing your image
If you are happy with your application at this point, you might want to push your image to Docker Hub. You will need to create an account on Docker Hub, then login from the command line:
1 |
$ docker login |
Only official images (such as iojs) can have simple names. To push your own image, you will need to change the name of the image. Instead of demo-app
, you will need to create the image using your login name. Mine is subfuzion
so my docker build
and docker push
commands would look like this:
1 2 3 4 |
$ docker build -t subfuzion/demo-app . ... $ docker push subfuzion/demo-app ... |
Exposing a port for a server
Modify the demo to make it an express app. Install express:
1 |
$ npm install --save express |
Then edit app.js
:
1 2 3 4 5 6 7 8 9 10 |
'use strict' const app = require('express')(); const port = process.env.PORT || 3000; app.use('/', function(req, res) { res.json({ message: 'hello world' }); }); app.listen(port); console.log('listening on port ' + port); |
And update Dockerfile
:
1 2 |
FROM iojs:onbuild expose 3000 |
Rebuild the image:
1 |
$ docker build -t demo-app . |
Now we can run it, but we want to map port 3000 inside the container to a port we can access from our system. We’ll pick port 49100:
1 |
$ docker run --rm -p 49100:3000 demo-app |
Now we can access the app via port 49100, which will be mapped to port 3000 in the container.
If you’re using boot2docker on a Mac, the port is actually mapped to the Docker host virtual machine. To determine the IP address, enter this at the command line:
1 2 |
$ echo $(boot2docker ip) 192.168.59.103 |
You should be able to test http://192.168.59.103:49100/
in your browser.
The app looks for the environment variable PORT to be set, otherwise it defaults to 3000. We could specify an alternate port via the environment from the command line like this:
1 |
$ docker run --rm -p 49100:8080 -e "PORT=8080" demo-app |
You will still access the app using the external port 49100, but it is now mapped to port 8080, which is what the app is listening to since the environment variable PORT was set.
Next time
We will cover the networking aspects in a bit more detail, show how you can mount the source instead of copying during development, and begin to cover the topic of orchestration, starting with a simple mongo dependency.