Running mocha via docker-compose doesn't work: "mocha: not found"


#1

I’ve got a vanilla Node.js project using docker-compose, and I’m trying to run my tests via mocha in the container. This works fine for me locally, but fails on CircleCI.

Relevant files:

Dockerfile:

FROM node:10.10.0-alpine

RUN apk update && apk add --no-cache build-base postgresql-dev

RUN mkdir -p /opt/my-service
WORKDIR /opt/my-service

COPY package*.json ./
RUN npm install

COPY . ./

CMD npm start

docker-compose.yml:

version: '3'

services:

  my-service-postgres:
    image: 'postgres:9.6-alpine'
    environment:
      POSTGRES_DB: 'postgres'
      POSTGRES_USER: 'postgres'
      POSTGRES_PASSWORD: 'postgres'
    volumes:
      - './initdb.sql:/docker-entrypoint-initdb.d/initdb.sql'
      - './db/data:/var/lib/postgresql/data'
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 10s
      retries: 6

  my-service-redis:
    image: 'redis:4.0-alpine'
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 10s
      retries: 6

  my-service-api:
    image: node:10.10.0-alpine
    depends_on:
      - my-service-postgres
      - my-service-redis
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/opt/my-service'
    env_file:
      - '.env'
    restart: on-failure

.circleci/config.yml:

version: 2

jobs:
  build:
    machine:
      image: circleci/classic:201711-01

    working_directory: ~/my-service

    steps:
      - run:
          name: Install required software
          command: |
            sudo apt-get install -y git python3 curl python-pip

      - checkout

      - run:
          name: Install awscli
          command: |
            pip install awscli --upgrade --user

      - run:
          name: Run tests
          command: |
            $(~/.local/bin/aws ecr get-login --no-include-email)
            docker-compose -p tests run -e "NODE_ENV=test" my-service-api npm test

When I run locally:

docker-compose -p tests run -e "NODE_ENV=test" my-service-api npm test
Starting tests_my-service-redis_1 ... done
Starting tests_my-service-postgres_1 ... done

> my-service@1.0.0 test /opt/my-service
> mocha

  GET /health
    ✓ responds with a 200 status (40ms)
    ✓ responds with "application/json; charset=utf-8" content type
    ✓ responds with a json response body

  3 passing (65ms)

When run in CircleCI build:

> my-service@1.0.0 test /opt/my-service
> mocha

sh: mocha: not found
npm ERR! Test failed.  See above for more details.
Exited with code 1

What am I doing wrong?


#2

Hmm, that is very odd. The benefit of DC is that you’re using images that ought to behave in the same way regardless of environment.

It looks like your node:10.10.0-alpine container cannot find the mocha binary. I would get an SSH session after a failed build, and then spin up that container like so:

docker run node:10.10.0-alpine sleep 10000

Then I’d get a shell on that container:

docker exec -it <containername> sh

Finally I’d see if Mocha can be found from the container’s perspective:

which mocha

Try this sequence locally too - there should be no differences in behaviour. If there is, you might be running different image versions, possibly?


#3

Thanks for replying @halfer . Note that mocha is a dev dependency in my package.json, and I do an “npm install”. So mocha does NOT get installed globally, but instead installed locally. This puts it in /opt/my-service/node_modules/. I had tried exactly what you had described by ssh’ing into a failed build to poke around.

circleci@default-dff78443-2e7e-4b36-9e40-cb20e84f0977:~$ docker commit c89b21ed4355 test
sha256:5cfbc5ac3133d6ca69ad82ac7f903f2334f3b3390e08abbbee90a4bd9e5dee5f
circleci@default-dff78443-2e7e-4b36-9e40-cb20e84f0977:~$ docker run -ti --entrypoint=sh test
/opt/my-service # cd node_modules/mocha/bin/
/opt/my-service/node_modules/mocha/bin # ll
total 32
drwxr-xr-x    2 root     root          4096 Sep 23 21:44 ./
drwxr-xr-x    5 root     root          4096 Sep 23 21:44 ../
-rwxr-xr-x    1 root     root         14619 Oct 26  1985 _mocha*
-rwxr-xr-x    1 root     root          2249 Oct 26  1985 mocha*
-rw-r--r--    1 root     root           897 Oct 26  1985 options.js
/opt/my-service/node_modules/mocha/bin # mocha
sh: mocha: not found
/opt/my-service/node_modules/mocha/bin # ./mocha 
Warning: Could not find any test files matching pattern: test
No test files found
/opt/my-service/node_modules/mocha/bin # cd ../../../
/opt/my-service # ./node_modules/mocha/bin/mocha 

  GET /health
    ✓ responds with a 200 status
    ✓ responds with "application/json; charset=utf-8" content type
    ✓ responds with a json response body

  3 passing (35ms)

As you can see, when I specify the whole path to mocha it works fine when I’m in the container. However, when I change the “test” command in package.json to be the full path to mocha, it still can’t find it.

Successfully tagged node:10.10.0-alpine
WARNING: Image for service my-service-api was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.

> my-service@1.0.0 test /opt/my-service
> /opt/my-service/node_modules/mocha/bin/mocha

sh: /opt/my-service/node_modules/mocha/bin/mocha: not found
npm ERR! Test failed.  See above for more details.
Exited with code 1

WTF? OK, so I added a step in Dockerfile to install mocha globally (npm install -g mocha) which helps it find mocha, but then it can’t find express:

...........snip............
Step 10/12 : RUN npm install -g mocha
 ---> Running in 123127131b60
/usr/local/bin/mocha -> /usr/local/lib/node_modules/mocha/bin/mocha
/usr/local/bin/_mocha -> /usr/local/lib/node_modules/mocha/bin/_mocha
+ mocha@5.2.0
added 24 packages from 436 contributors in 1.003s
Removing intermediate container 123127131b60
 ---> ed8caef98469
Step 11/12 : COPY . ./
 ---> c6912fcc9d98
Step 12/12 : CMD npm start
 ---> Running in d972bf71103e
Removing intermediate container d972bf71103e
 ---> 4bd6c4f74874
Successfully built 4bd6c4f74874
Successfully tagged node:10.10.0-alpine
WARNING: Image for service my-service-api was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.

> my-service@1.0.0 test /opt/my-service
> mocha

internal/modules/cjs/loader.js:583
    throw err;
    ^

Error: Cannot find module 'express'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
    at Function.Module._load (internal/modules/cjs/loader.js:507:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (/opt/my-service/test/helper.js:3:17)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at /usr/local/lib/node_modules/mocha/lib/mocha.js:250:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/usr/local/lib/node_modules/mocha/lib/mocha.js:247:14)
    at Mocha.run (/usr/local/lib/node_modules/mocha/lib/mocha.js:576:10)
    at Object.<anonymous> (/usr/local/lib/node_modules/mocha/bin/_mocha:637:18)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:696:3)
npm ERR! Test failed.  See above for more details.
Exited with code 1

So there’s clearly something fundamentally wrong with my setup that I’m just not spotting. Any other help greatly appreciated!


#4

I’m not familiar with JS, but this error:

is not the same as the Linux path error previously. I agree it’s odd, since it looks like Docker images are not behaving consistently. However, as I said before, they may not be the same image.

I believe you can supply an env var to a JS app to indicate where to find the libraries dependencies. I don’t know what that var is, but I am sure a search engine search would reveal it.


#5

@halfer Yes, I understand it’s a different flavor of error.

I mean, everything I’ve done above indicates they are the same image. Not sure what else I can do to confirm/deny that.


#6

Do docker images and check the IMAGE ID is the same across local and remote.

However, I would not spend too much time on that, since it still needs fixing. Try the env var in the JS environment to point to the dependencies.


#7

As I understand it the image id by it’s nature will not be the same across different environments since I’m building a new image based on node:10.10.0-alpine. But yes, I tried setting NODE_PATH (the env var in question) and it has no effect. :-/


#8

Figured it out. I had incorrectly mounted the local directory as a volume, which is NOT mounted during a build. As a result, node_modules isn’t present in /opt/my-service. I had node_modules on my local machine, which is why it works for me locally.

So to sum up, don’t do this:

    volumes:
      - '.:/opt/my-service'

Hope this helps someone in the future.


#9

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.