How do I get node.js “live editing” to work with Docker

My weekend project was to explore Docker and I thought a simple node.js project would be good. By “live edit” I mean I’d like to be able to manipulate files on my host system and (with as little effort as possible) see the Docker container reflect those changes immediately.

The Dockerizing a Node.js web app went smoothly, and then the Googling and thrashing began. I think that I now know the following:

  • Docker: setctty operation not permitted
  • guacamole You do not have permission to access this connection"
  • Docker Run error with ENTRYPOINT
  • docker api ContainerExecInspect cannot get correct exit code
  • What is the gcloud API for the Google Container Registry
  • Creating a Docker UAT/Production image
    • If I use the ADD method noted on the nodejs tutorial, then I can’t have live edit because ADD is fulfilled completely at docker build (not at docker run).

    • If I mount the node project’s directory with something like -v `pwd`:/usr/src/app, then it won’t run because either node_modules doesn’t exist (and the volume is not available at build time to get populated because -v is a docker run argument) or I need to prepopulate node_modules in the host’s project directory, which just doesn’t feel right and could have OS compatibility issues.

    My mad newbie thrashing can be distilled to three attempts, each with their own drawbacks or apparent failures.

    1) The Node.js tutorial using ADD Works perfectly, but no “live edit”. I have no expectation this should be what I need, but it at least proved I have the basic wiring in place and running.

    FROM node:argon
    
    # Create app directory
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    
    # Install app dependencies
    COPY package.json /usr/src/app/
    RUN npm install
    
    # Bundle app source
    COPY . /usr/src/app
    
    EXPOSE 8080
    CMD [ "npm", "start" ]
    

    2) Try building the node dependencies as globals from the Dockerfile. This seemed less cool, but reasonable (since I wouldn’t expect to change dependencies often). However, it also simply didn’t work, which really surprised me.

    FROM node:argon
    
    RUN npm install -g express
    
    WORKDIR /usr/src/app
    # which will be added via `docker run -v...`
    
    EXPOSE 8080
    

    3) Upon build, ADD only the package.json to a temporary location and get node_modules set up, then move that to the host’s project directory, then mount the project’s directory with -v `pwd`:/usr/src/app. If this would have worked I’d have tried to add nodemon and would theoretically have what I want. This seemed to me to be by far the most clever and commonsense, but this simply didn’t work. I monkeyed with a few things attempting to fix, including host directory permissions, and had no joy.

    FROM node:argon
    
    WORKDIR /usr/src/app
    
    # Provides cached layer for node_modules
    ADD package.json /tmp/package.json
    RUN cd /tmp && npm install
    RUN cp -a /tmp/node_modules /usr/src/app/
    
    EXPOSE 8080
    

    I suspect there are multiple instances of me not understanding some basic concept, but as I searched around it seemed like there were a lot of different approaches, sometimes complicated by additional project requirements. I was, perhaps foolishly, trying to keep it simple. 🙂

    Stats:

    • Running on Mac OS 10.10
    • Docker 1.12.0-rc2-beta17 (latest at time of writing)
    • Kitematic 0.12.0 (latest at time of writing)
    • Node.js 4.4.7 (latest pre-6 at time of writing)

    UPDATE: Attempting to pull some of what I’ve tried to learn together, I’ve done the following and had better luck. Now it builds but docker run -v `pwd`:/usr/src/app -p 49160:8080 -d martink/node-docker3 doesn’t stay running. However, I can “Run” and “Exec” in Kiteomatic and in shell I can see that node_modules looks good and has been moved into the right place, and I can manually do node server.js and have joy.

    FROM node:argon
    
    # Copy over the host's project files
    COPY . /usr/src/app
    # This provides a starting point, but will later be overridden by `-v`, I hope
    
    # Use this app directory moving forward through this file
    WORKDIR /usr/src/app
    
    # PULL TOGETHER `NODE_MODULES`
    ## Grab the package.json from the host and copy into `tmp`
    COPY package.json /tmp/package.json
    
    ## Use that to get `node_modules` set up in `tmp`
    RUN cd /tmp && npm install
    
    ## Copy that resulting node_modules into the WORKDIR
    RUN cp -a /tmp/node_modules /usr/src/app/
    
    EXPOSE 8080
    

    I think my questions might now be whittled down to…

    • How do I make server.js start when I run this?
    • How do I see “live edits” (possibly start this with nodemon)?

  • Limit JVM memory consumption in a Docker container
  • I'm having trouble using docker-py in a development environment on OSX
  • Dockerfile RUN command returning “No such file or directory”
  • Communication between two Docker containers
  • how to deploy Kubernetes nginx controller with kubeadm (k8s 1.4)?
  • Why does the new built image does not have the new installed package?
  • One Solution collect form web for “How do I get node.js “live editing” to work with Docker”

    It appears this Dockerfile gets me what I need.

    FROM node:argon
    
    # Adding `nodemon` as a global so it's available to the `CMD` instruction below
    RUN npm install -g nodemon
    
    # Copy over the host's project files
    COPY . /usr/src/app
    # This provides a starting point, but will later be overridden by `docker run -v...`
    
    # Use this app directory moving forward through this file
    WORKDIR /usr/src/app
    
    # PREPARE `NODE_MODULES`
    ## Grab the `package.json` from the host and copy into `tmp`
    COPY package.json /tmp/package.json
    
    ## Use that to get `node_modules` set up
    RUN cd /tmp && npm install
    
    ## Copy that resulting `node_modules` into the WORKDIR
    RUN cp -a /tmp/node_modules /usr/src/app/
    
    EXPOSE 8080
    CMD [ "nodemon", "./server.js" ]
    

    I build this like so:

    docker build -t martink/node-docker .
    

    And run it like so:

    docker run -v `pwd`:/usr/src/app -p 49160:8080 -d  martink/node-docker
    

    This…

    • Stays running
    • Responds as expected at http://localhost:49160
    • Immediately picks up changes to server.js made on the host machine’s file

    I’m happy that it seems I have this working. If I’ve any bad practices in there, I’d appreciate feedback. 🙂

    Docker will be the best open platform for developers and sysadmins to build, ship, and run distributed applications.