简体   繁体   中英

Node.js dev environment in Docker on Windows

I have tried everything I can think of. I have read the docs, blogs and tried following samples on github.

But I can't seem to get it to work.

What I want to do is simple. I want to write my node.js code on my windows 8.1 machine, and I also want to run the code from within a Docker container without having to rebuild the container all the time. So I want to map a directory on my Windows Host to a directory inside the container.

I have created this Dockerfile

FROM node:0.10.38

RUN apt-get update -qq && apt-get install -y build-essential

ENV ZMQ_VERSION 4.1.3
ENV LIBSODIUM_VERSION 1.0.3

RUN curl -SLO "https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VERSION.tar.gz" \
    && tar xvf libsodium-$LIBSODIUM_VERSION.tar.gz \
    && cd libsodium-$LIBSODIUM_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r libsodium-$LIBSODIUM_VERSION \
    && rm libsodium-$LIBSODIUM_VERSION.tar.gz
RUN curl -SLO "http://download.zeromq.org/zeromq-$ZMQ_VERSION.tar.gz" \
    && tar xvf zeromq-$ZMQ_VERSION.tar.gz \
    && cd zeromq-$ZMQ_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r zeromq-$ZMQ_VERSION \
    && rm zeromq-$ZMQ_VERSION.tar.gz
RUN ldconfig

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

RUN mkdir -p /usr/src/app
ADD . /usr/src/app
WORKDIR /usr/src/app
RUN npm install

EXPOSE 3000
EXPOSE 35729

CMD ["npm", "start"]

I have this simple server.js file

var express = require('express');
var app = express();
var zmq = require('zmq');

app.get('/', function (req, res) {
  res.send('ZMQ: ' + zmq.version);
});

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});

And this simple package.json

{
  "name": "docker-node-hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.13.3",
    "zmq": "^2.14.0"
  }
}

I have installed the latest Docker Toolbox, and can run the Docker Hello World example fine. I try to build my docker image like this when I am in the directory where my Dockerfile is.

docker build -t dockernodetest:dockerfile .

I then try to run it, also from the same location inside the Docker Quickstart Terminal. I use the hash since the tag doesn't take for some reason:

docker run -v //c/Bitbucket/docker-node-hello-world/:/usr/src/app -p 3000:3000 -i -t 9cfd34e046a5 ls ./usr/src/app

The result of this is an empty directory. I was hoping I could just invoke

docker run -v //c/Bitbucket/docker-node-hello-world/:/usr/src/app -p 3000:3000 -i -t 9cfd34e046a5 npm start

But since the host directory isn't available it fails. I have the feeling that I have misunderstood something very basic. I just don't know what.

First let's start from the Dockerfile

FROM node:0.10.38-onbuild

RUN apt-get update -qq && apt-get install -y build-essential

ENV ZMQ_VERSION 4.1.3
ENV LIBSODIUM_VERSION 1.0.3

RUN curl -SLO "https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VERSION.tar.gz" \
    && tar xvf libsodium-$LIBSODIUM_VERSION.tar.gz \
    && cd libsodium-$LIBSODIUM_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r libsodium-$LIBSODIUM_VERSION \
    && rm libsodium-$LIBSODIUM_VERSION.tar.gz
RUN curl -SLO "http://download.zeromq.org/zeromq-$ZMQ_VERSION.tar.gz" \
    && tar xvf zeromq-$ZMQ_VERSION.tar.gz \
    && cd zeromq-$ZMQ_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r zeromq-$ZMQ_VERSION \
    && rm zeromq-$ZMQ_VERSION.tar.gz
RUN ldconfig

EXPOSE 3000 35729

From line 1 I've used the 0.10.38-onbuild tag because I want to take advantage of onbuild scripts that will run to create the /usr/src/app directory and run npm install

Then server.js and package.json are as you have written them. These are both in the same working directory as the Dockerfile as well.

Next we build the image

docker build -t dockernodetest .

I've omitted the dockerfile tag as it seemed unnecessary. The client will automatically add a latest tag anyway. To see what images you've have locally run docker images .

At this point we should have an image ready to run but let us first check that the files we wanted to load are then and that npm install created the node_modules directory

$ docker run dockernodetest ls /usr/src/app
Dockerfile
node_modules
package.json
server.js

We're ready at this point to run our little nodejs app

$ docker run -it -p 8080:3000 dockernodetest

> docker-node-hello-world@1.0.0 start /usr/src/app
> node server.js

Example app listening at http://0.0.0.0:3000

In this instance I've used the -p 8080:3000 flag to map the container's 3000 port to port 8080 on my host machine. Note that I didn't have any other commands at the end because the -onbuild image I've pulled form has a CMD [ "npm", "start" ] and so the default action is to run the start package script.

So to make the development cycle even faster you want to mount your working directory to the container via the -v option

$ docker run -it -p 8080:3000 -v "$PWD":/usr/src/app dockernodetest

> docker-node-hello-world@1.0.0 start /usr/src/app
> node server.js


module.js:340
    throw err;
          ^
Error: Cannot find module 'express'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/usr/src/app/server.js:1:77)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)

npm ERR! Linux 4.1.7-15.23.amzn1.x86_64
npm ERR! argv "node" "/usr/local/bin/npm" "start"
npm ERR! node v0.10.38
npm ERR! npm  v2.11.1
npm ERR! code ELIFECYCLE
npm ERR! docker-node-hello-world@1.0.0 start: `node server.js`
npm ERR! Exit status 8

But what's happened here? Because we mounted the current working directory it overwrote what was previously there in /usr/src/app including our node_modules directory.

So the quick fix here is to now run a npm install in our current working directory and rerun the docker run command.

$ npm install
docker-node-hello-world@1.0.0 /home/ec2-user/dockernode
└─┬ express@4.13.3
  ├─┬ accepts@1.2.13
  │ ├─┬ mime-types@2.1.7
...
$ docker run -it -p 8080:3000 -v "$PWD":/usr/src/app dockernodetest

> docker-node-hello-world@1.0.0 start /usr/src/app
> node server.js

Example app listening at http://0.0.0.0:3000

Now if you make an update to your server.js file just hit Ctrl + c and restart you docker image though you might want to use something like nodemon to make this even more seamless.

Thank you so much for your answer. It definitely led me on the right path. It took me longer than I had thought, but I got it working.

There were a few things that didn't work, but I got it working at last. Here is a rundown of what I went through.

  1. I wasn't able to use the onbuild scripts as you suggested. The reason being that when I base my image on the onbuild script it runs npm install before I install ZeroMQ.

  2. Running npm install outside my docker image wasn't an option either as you suggested for when I mapped a drive. One of the reasons I want to run this in Docker is that I want an environment where all dependencies (such as ZeroMQ) are installed and available. This is something that on some environments is difficult, and I want to run this on both windows and linux hosts.

  3. I really want to use nodemon for when I develop so I also had to install this globally in the image, and also invoke nodemon when starting the container. So I had to extend your example as you suggested.

  4. I had a lot of trouble getting the mapped volumes to work. It turns out that on a windows host you have to be in the context of C:\\users\\<username> to be able to map volumes on the host from the container. Once I figured that out and undid all the weird experiments I had been through to make it all work, I got that to work. Using "$PWD" as you suggested when invoking docker run is also weird on a windows host. You have to prefix with a forward slash like this: /"$PWD" .

  5. When I figured it all out I started looking at docker compose, partly because I didn't want to keep typing out the long docker run commands (that I kept getting wrong) but also because in my real projects I want multiple containers for my database and other services I need.

So this is what it looks like now. It works exactly the way I want it to work. I now have a container where all dependencies are installed inside the container and whenever I run my container it first invokes npm install and the nodemon server.js . All files including the installed npm modules are on the host but mapped inside the container where everything is run from.

File 1 - docker-compose.yml (notice I don't need the $PWD variable, but can just use a . relative path for the host path)

web:
  build: .
  volumes:
    - ".:/usr/src/app"
  ports:
    - "3000:3000"

File 2 - Dockerfile

FROM node:0.10.40

RUN mkdir /usr/src/app
RUN mkdir /usr/src/node_modules

RUN apt-get update -qq && apt-get install -y build-essential

ENV ZMQ_VERSION 4.1.3
ENV LIBSODIUM_VERSION 1.0.3

RUN curl -SLO "https://download.libsodium.org/libsodium/releases/libsodium-$LIBSODIUM_VERSION.tar.gz" \
    && tar xvf libsodium-$LIBSODIUM_VERSION.tar.gz \
    && cd libsodium-$LIBSODIUM_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r libsodium-$LIBSODIUM_VERSION \
    && rm libsodium-$LIBSODIUM_VERSION.tar.gz
RUN curl -SLO "http://download.zeromq.org/zeromq-$ZMQ_VERSION.tar.gz" \
    && tar xvf zeromq-$ZMQ_VERSION.tar.gz \
    && cd zeromq-$ZMQ_VERSION \
    && ./configure \
    && make \
    && make install \
    && cd .. \
    && rm -r zeromq-$ZMQ_VERSION \
    && rm zeromq-$ZMQ_VERSION.tar.gz
RUN ldconfig

RUN npm install -g nodemon
WORKDIR /usr/src/app

CMD export NODE_PATH=/usr/src/node_modules && cp package.json /usr/src && npm install --prefix /usr/src && npm start

EXPOSE 3000 35729

File 3 - package.json (notice I use the -L flag when invoking nodemon. This is needed when running inside a container)

{
  "name": "docker-node-hello-world",
  "version": "1.0.0",
  "description": "node hello world",
  "main": "server.js",
  "scripts": {
    "start": "nodemon -L server.js"
  },
  "author": "Author",
  "license": "ISC",
  "dependencies": {
    "express": "^4.13.3",
    "zmq": "^2.14.0"
  }
}

File 4 - server.js

var express = require('express');
var app = express();
var zmq = require('zmq');

app.get('/', function (req, res) {
  res.send('ZMQ Version: ' + zmq.version);
});

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});

To build use the following command

docker-compose build

To run use the following command

docker-compose up

At this time I can develop my app on my host, and whenever I change a file my node server is restarted. When I add a new dependency to my package.json I have to restart my container to run npm install .

The only thing I don't have working yet is how I make rebuilding my image a smooth operation. What happens is that after I build a new image I have to delete my old container before I can run a new container. The mapping is locked by the old container.

I would really like to give you the credit @jeedo. I would not have reached this solution without your help. I don't know how to do that and also mark this as the solution.

Edit: I just edited this to add something to my Dockerfile. I moved the node_modules folder away from the host filesystem. I had some trouble with paths becoming too long on Windows. These changes make sure the node_modules are always installed inside the container.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM