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

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?

1 Like

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?

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!

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.

@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.

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.

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. :-/

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.

2 Likes

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