6

PROBLEM

Hey, I have not used Docker much - I am trying to run my Jest tests through the Dockerfile. However, I'm getting this error when trying to build image:

ERROR

Step 13/16 : RUN if [ "$runTests" = "True" ]; then     RUN npm test; fi
 ---> Running in ccdb3f89fb79
/bin/sh: RUN: not found

Dockerfile

FROM node:10-alpine as builder
ARG TOKEN
WORKDIR /app

ARG runTests


COPY .npmrc-pipeline .npmrc

COPY package*.json ./
RUN npm install
COPY . .

RUN rm -f .npmrc

ENV PORT=2000
ENV NODE_ENV=production


RUN if [ "$runTests" = "True" ]; then \
    RUN npm test; fi

RUN npm run build

EXPOSE 2000

CMD ["npm", "start"]

The command I am using to build the image is this, and the idea is to be able to run the tests only when runTests=True.

docker build -t d-image --build-arg runTests="True" --build-arg "MY TOOOOOKEN"

Is this possible to do by just using the Dockerfile? Or is it necessary to use docker-compose as well?

The conditional statement seems to work good.

Not posssible to have two CMD commands

I have tried this as a workaround (but it did not work): Dockerfile

FROM node:10-alpine as builder
ARG TOKEN
WORKDIR /app

ARG runTests


COPY .npmrc-pipeline .npmrc

COPY package*.json ./
RUN npm install
COPY . .

RUN rm -f .npmrc

ENV PORT=3000
ENV NODE_ENV=production


RUN npm run build

EXPOSE 3000

CMD if [ "$runTests" = "True" ]; then \
    CMD ["npm", "test"] && ["npm", "start"] ;fi

Now I'm not getting any output from the test, but it looks to be successful.

PROGRESS

I have made some progress and the tests are actually running when I'm building the image. I also decided to use the RUN command for running the tests, so that they run on the build step.

Dockerfile:

FROM node:10-alpine as builder
ARG TOKEN
WORKDIR /app


COPY .npmrc-pipeline .npmrc

COPY package*.json ./
RUN npm install
COPY . .
RUN rm -f .npmrc
ENV PORT=3000
ENV NODE_ENV=production
RUN npm run build
RUN npm test
EXPOSE 3000

Error:

FAIL src/pages/errorpage/tests/accessroles.test.jsx
  ● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

It seems to me that the docker build process does not use the jest:{...} configurations in my package.json, even though it is copied and installed in the Dockerfile Any ideas?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
meerkat
  • 932
  • 2
  • 14
  • 38
  • I'd go for CMD, like pointed here: https://stackoverflow.com/questions/60074174/how-can-i-run-an-npm-command-in-a-docker-container – sleepwalker Nov 23 '20 at 09:56
  • Does this answer your question? [Dockerfile if else condition with external arguments](https://stackoverflow.com/questions/43654656/dockerfile-if-else-condition-with-external-arguments) – mrak Nov 23 '20 at 11:15
  • The conditional statement I got to work already in the post, but thanks. – meerkat Nov 23 '20 at 11:27
  • @sleepwalker problem is that it's only possible to have one CMD command per Dockerfile. Updated the question – meerkat Nov 23 '20 at 11:31
  • It looks like you're repeating the Docker `RUN`/`CMD` line inside the shell `if ... then ... fi` construct. You don't need to do that; just remove the second `RUN`/`CMD`. (The build step will run unconditionally, but will do nothing if the test fails.) – David Maze Nov 23 '20 at 14:19

2 Answers2

8

RUN and CMD aren't commands, they're instructions to tell Docker what do when building your container. So e.g.:

RUN if [ "$runTests" = "True" ]; then \
    RUN npm test; fi

doesn't make sense, RUN <command> runs a shell command but RUN isn't defined in the shell, it should just be:

ARG runTests  # you need to define the argument too
RUN if [ "$runTests" = "True" ]; then \
    npm test; fi

The cleaner way to do this is to set up npm as the entrypoint, and start as the specific command:

ENTRYPOINT [ "npm" ]
CMD [ "start" ]

This allows you to build the container normally, it doesn't require any build arguments, then run an NPM script other than start in the container, e.g. to run npm test:

docker run <image> test

However, note that this means all of the dev dependencies need to be in the container. It looks (from ENV NODE_ENV=production) like you intend this to be a production build, so you shouldn't be running the tests in the container at all. Also despite having as builder this isn't really a multi-stage build. The idiomatic script for this would be something like:

# stage 1: copy the source and build the app

FROM node:10-alpine as builder
ARG TOKEN

WORKDIR /app

COPY .npmrc-pipeline .npmrc

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# stage 2: copy the output and run it in production

FROM node:10-alpine

WORKDIR /app

ENV PORT=3000
ENV NODE_ENV=production

COPY --from=builder /app/package*.json ./
RUN npm ci

COPY --from=builder /* your build output */

EXPOSE 3000

ENTRYPOINT [ "npm" ]
CMD [ "start" ]

See e.g. this Dockerfile I put together for a full-stack React/Express app.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Why `RUN npm ci` again instead of copying node modules between stages? – OneCricketeer Sep 14 '22 at 06:29
  • @OneCricketeer because you don't want all the dev dependencies in the final container; I suppose you could copy the whole directory then [prune](https://docs.npmjs.com/cli/v8/commands/npm-prune) it instead. – jonrsharpe Sep 14 '22 at 21:21
  • Right, that's what I was thinking, too, however, I believe that might cause extra storage in a Docker layer since the `RUN npm prune` would be a new layer than the `COPY --from=builder node_modules`... I haven't really tested it, but working on refactoring some NodeJS project – OneCricketeer Sep 14 '22 at 21:36
0

A couple things on this:

2 commands are possible (but NOT recommended) , as I did here: https://hub.docker.com/repository/docker/djangofan/mountebank-with-ui-node

Also, I wouldn't recommend that your tests run while building your container image. Instead, build the container image so that it maps a folder to the location of your test files. Then, include your "temporary test image" in your compose file.

version: '3.4'
services:
  service-api:
    container_name: service-api
    build:
      context: .
      dockerfile: Dockerfile-apibase
    ports:
      - "8083:8083"
  e2e-tests:
    container_name: e2e-tests
    build:
      context: .
      dockerfile: Dockerfile-testbase
    command: bash -c "wait-for-it.sh service-api:8083 && gradle -q clean test -Dorg.gradle.project.buildDir=/usr/src/example"

Then execute it like so to get a 0 or 1 exit code:

docker-compose up --exit-code-from e2e-tests

After running that, the service will remain running but the tests will shutdown when finished.

Hopefully that makes sense even though the example I gave is not exactly like your situation. Here is a LINK to my example from above, which you can try yourself. It should work similarly for Jest tests.

djangofan
  • 28,471
  • 61
  • 196
  • 289