Reputation: 1471
I am trying to convert the following command into JSON Array.
From this
CMD $(npm bin)/knex migrate:latest && $(npm bin)/knex seed:run && npm run start
To this
CMD ["$(npm bin)/knex", "migrate:latest", "&&", "$(npm bin)/knex", "seed:run", "&&", "npm", "run", "start"]
But it gives me this error. It seems the be the problem of path.
internal/modules/cjs/loader.js:888
throw err;
^
Error: Cannot find module '/$(npm bin)/knex'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:885:15)
at Function.Module._load (internal/modules/cjs/loader.js:730:27)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Why is that? This is my dockerfile config
FROM node:14-alpine
WORKDIR /
EXPOSE 8080
COPY ./package*.json /
RUN ["npm", "install"]
COPY ./. /
# CMD $(npm bin)/knex migrate:latest && $(npm bin)/knex seed:run && npm run start
CMD ["$(npm bin)/knex", "migrate:latest", "&&", "$(npm bin)/knex", "seed:run", "&&", "npm", "run", "start"]
Upvotes: 1
Views: 1717
Reputation: 159752
To answer the specific question you asked: there are two forms of CMD
(and ENTRYPOINT
and RUN
). The JSON-array form is interpreted directly, without running a shell or performing any sort of expansion; anything else is interpreted as though it was run with sh -c
.
# the same:
CMD any command string
CMD ["/bin/sh", "-c", "any command string"]
In the command you show, you must run some shell to perform the $(npm bin)
subshell expansion, and to run multiple command1 && command2
. You could manually wrap the command string you show in the JSON-array ["sh", "-c", "..."]
form, but there's no real benefit to it. With that specific CMD
, the form you have in the question is the best you can do.
This having been said, if you have a very involved CMD
like this, you might consider whether you can break it up into smaller parts. The commands you show do some first-time setup, and then launch the main container command. You can turn this into a script:
#!/bin/sh
# entrypoint.sh
#
# Do the first-time setup:
npx knex migrate:latest
npx knex seed:run
# Launch the main container command:
exec "$@"
In your Dockerfile, COPY
this script into your image (if it's not already there) and set it as your ENTRYPOINT
. You also need to provide the CMD
to run. With both an ENTRYPOINT
and a CMD
, the CMD
is passed as arguments to the ENTRYPOINT
, and so the final exec "$@"
line runs the CMD
.
COPY entrypoint.sh . # if it's not already copied in
ENTRYPOINT ["./entrypoint.sh"] # must be JSON-array form
CMD ["npm", "run", "start"] # either JSON-array or string form
One thing that's useful to do with this setup is to launch a temporary container with a debugging shell:
docker run --rm -it your-image /bin/sh
This replaces the CMD
, but leaves the ENTRYPOINT
intact. This runs the wrapper script and does the first-time setup (in this case, the database migrations), but then launches the shell instead of your application. Now you can check what's actually happened in the setup phase and debug it.
Upvotes: 5