Redsandro
Redsandro

Reputation: 11356

Docker automatically rerun npm install on package.json change

I'm trying to set up development containers for a node.js project so that my local source files are shared with the guest (so I can dnsmasq wildcard requests to a local domain over port 80, but I guess that's irrelevant to the question). When I make a change locally, the node process in the container is restarted:

Dockerfile:

FROM node:8

# Install app dependencies
RUN npm install -g nodemon
COPY package.json /tmp/package.json
RUN cd /tmp && npm install --production

NODE_PATH=/tmp/node_modules
WORKDIR /app

EXPOSE 8080
EXPOSE 9229

CMD nodemon --inspect index.js

docker-compose.json (truncated):

version: '3.3'
services:
    app:
        build: .
        container_name: 'my-app'
        restart: unless-stopped
        ports:
            - 8080:8080
            - 9229:9229
        volumes:
            - .:/app

Because the node_modules are installed in the Dockerfile, every time I add a package to packages.json, I have to manually re-build the container:

docker-compose stop
docker-compose build
docker-compose up -d

I could set up a watch for this using inotify, but I don't like to stop the entire stack (including other services defined there).

Is it possible to have this logic inside the container, and re-run npm install every time package.json is changed?

Upvotes: 6

Views: 8024

Answers (2)

Redsandro
Redsandro

Reputation: 11356

Solution

I wrote an entry file that starts two threads in node.js, so Dockerfile can be very vanilla. Now all we need is docker-compose.

Dockerfile

FROM node:8
RUN npm install -g nodemon

docker-compose.yml

version: '3.5'
services:
    app:
        build: .
        user: '1000'
        working_dir: /opt/app
        ports:
            - 8080:8080
            - 9229:9229
        volumes:
            - ./:/opt/app
        command: 'node docker-entry.js'

docker-entry.js

/**
 * @file docker-entry for development container
 * @author Redsandro (https://www.windowsremix.com/)
 */
'use strict'
const { spawn } = require('child_process')

/*
 * Install dependencies every time package.json changes
 */
spawn('nodemon -w package.json --exec "npm install"', {
    stdio: 'inherit',
    shell: true
})

/*
 * Restart node when a source file changes, plus:
 * Restart when `npm install` ran based on `package-lock.json` changing.
 */
spawn('nodemon --inspect -e js,json -i node_modules -i package.json index.js', {
    stdio: 'inherit',
    shell: true
})

.dockerignore

# Just ignore everything
**

This works because package-lock.json changes when dependencies were added or updated. So npm 5 is required. Untested on other node images.

Make sure there is no node_modules or package-lock.json in your working directory on the first run. The internal node app will crash on first run (obviously, it cannot find the dependencies) but it will restart once the dependencies are installed.

Enter docker-compose up -d && docker-compose logs -f to see the results. Make some changes to the files, and add a package to package.json.

Every time you start the container, npm install will run, which is not necessary 9 out of 10 times unless you change package.json while the container was stopped. This is fine, because the node application is started in parallel, you don't have to wait for this to finish. You can use the container immediately. (Unless this is your first run of course.)

To do: I had hoped to move node_modules to some guest directory (e.g. /tmp) so all this data would be stored on the docker server rather than on my laptop ssd. More importantly, your host and guest share the same node_modules now. If you use a non-linux OS and compiled packages, you're gonna have a bad time. Because it will either work in the host, or in the guest, but not in both. I can't seem to specify a different installation directory for npm like I did before without npm writing to package-lock.json there too, and that file is necessary in the local directory for this setup to work. I'm sure with some clever linking it can work, but I've exhausted the time I had allotted for this solution. Tips are welcome. Feel free to add addendums and other answers.

Upvotes: 2

Teukros
Teukros

Reputation: 82

Is it possible to have this logic inside the container, and re-run npm install every time package.json is changed?

It is not fully clear to me, if your changes in package.json are done on host or in docker container in /tmp/ directory. I guess you modify a file on host since you have other project files there.

If you modify package.json in other location than /tmp/ in your container, you need to manually copy new version of package.json to /tmp in container

docker cp "location/of/package.json" "containername":/tmp/

then you just enter the container and install dependencies.

docker exec -it containername bash
cd /tmp/
npm install

Is it possible to have this logic inside the container, and re-run npm install every time package.json is changed?

You could pass logic described above to simple bash script. Then you can run some watcher (for example nodemon) to observe package.json and run the script after file change. Maybe it would be also good idea to pass big delay so it doesn't try to install a module before you even type full name of it. But in most situations I guess you just use npm command instead of typing name of the module manually.

Upvotes: 2

Related Questions