NodeJS fs.watch inside Docker Container detects changes twice

I've a Node JS file that will watch changes on a folder. It worked good until I decided to migrate it to a Docker Container. I've made a minimal example to recreate.

This is my NodeJS code:

const express = require('express');
const server = express();
const PORT = process.env.PORT || 3000;

const fs = require('fs');
const {
    join
} = require('path');


const dirs = p => fs.readdirSync(p).filter(f => fs.statSync(join(p, f)).isDirectory());

const watchingPath= `../${process.env.WATCH}` || "../watchingFolder";
const watching = dirs(watchingPath);
console.log(`Watching ${watching} šŸ‘€`);



watching.forEach(dir => {
    var path = `${watchingPath}/${dir}/`;
    fs.watch(path, (eventType, filename) => {
        fs.readFile(`${path}${filename}`, async (err, data) => {
            if (err) return;
            console.log(`${path} got ${filename}`);
        });
    });
});

server.listen(PORT, () => console.log(`Server running on ${PORT}`));

Locally im watching the folder ../watchingFolder which is binded to a folder in the container named /wFolder. The binding can be seen on the docker-compose.yml:

version: "3"
services:
  eye: 
    build: ./EYE
    container_name: watchTest
    ports: 
      - 80:3000
    restart: on-failure
    environment:
      - PORT=3000
      - WATCH=wfolder
    volumes: 
      - ./watchingFolder:/wfolder

and the Dockerfile for the image is:

FROM node:latest
RUN mkdir /eye
RUN mkdir /wfolder
ADD . /eye
WORKDIR /eye
RUN npm i
EXPOSE 80
CMD ["npm", "start"]

Now it works fine but for some reason whenever I move a file in my Machine, instead of printing the changes once, it does it Twice.

I'd like to know why does Docker behaves like this.

Thanks.

Upvotes: 2

Views: 2956

Answers (1)

Matt
Matt

Reputation: 74761

tldr; debounce file system events for the same path/type.


The events that fs.watch receives are operating system dependant as they rely on the underlying "file event watching" implementation of each OS.

In this case there is also an intermediate file system driver between the host and the Docker VM running the containers that does some translation, so may also change what you would see normally triggered from a plain Linux OS compared to what you are seeing from macOS.

If you install the inotify-tools package into the container you can see a little more detail on the type of CHANGE node is observing.

Run inotifywait in the container

root@4210a3a174cc:/app# apt-get update
root@4210a3a174cc:/app# apt-get install -y inotify-tools
root@4210a3a174cc:/app# inotifywait -m /wfolder
Setting up watches.
Watches established.

Then create a file in the container:

ā†’ docker exec 4210a3a174cc bash -c 'echo test > /wfolder/test_in_docker'

Results in:

root@4210a3a174cc:/app# inotifywait -m /wfolder
Setting up watches.
Watches established.
/wfolder/ CREATE test_in_docker
/wfolder/ OPEN test_in_docker
/wfolder/ MODIFY test_in_docker
/wfolder/ CLOSE_WRITE,CLOSE test_in_docker

Then create a file outside the container:

mac$ echo test > wfolder/test_out_of_docker

Results in:

root@4210a3a174cc:/app# inotifywait -m /wfolder
Setting up watches.
Watches established.
/wfolder/ CREATE test_out_of_docker
/wfolder/ MODIFY test_out_of_docker

So there is a file creation, and modify when you put a new file there. You will also notice that cp, mv and rm behave differently from the host and inside the container and normally produce more than 1 event (except for rm inside the container)

In all cases though you will get a batch of events in a short period of time, so you can usually debounce them into one call.

Upvotes: 4

Related Questions