I have this simple node.js image:
FROM node:12
USER root
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm i --production
COPY . .
ENTRYPOINT node dist/main.js
ultimately, I just want to be able to pass different arguments to node dist/main.js like so:
docker run -d my-image --foo --bar=3
so that the executable when run is
node dist/main.js --foo --bar=3
I have read about CMD / ENTRYPOINT and I don't know how to do this, anybody know?
I would suggest writing a custom entrypoint script to handle this case.
In general you might find it preferable to use CMD to ENTRYPOINT in most cases. In particular, the debugging shell pattern of
docker run --rm -it myimage sh
is really useful, and using ENTRYPOINT to run your main application breaks this. The entrypoint script pattern I’m about to describe is also really useful in general and it’s easy to drop in if your main container process is described with CMD.
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["node", "dist/main.js"]
The script itself is an ordinary shell script that gets passed the CMD as command-line arguments. It will typically end with exec "$#" to actualy run the CMD as the main container process.
Since the entrypoint script is a shell script, and it gets passed the command from the docker run command line as arguments, you can do dynamic switching on it, and meet both your requirement to just be able to pass additional options to your script and also my requirement to be able to run arbitrary programs instead of the Node application.
#!/bin/sh
if [ $# = 1 ]; then
# no command at all
exec node dist/main.js
else
case "$1" of
-*) exec node dist/main.js "$#" ;;
*) exec "$#" ;;
esac
fi
This seems to work:
ENTRYPOINT ["node", "dist/main.js"]
CMD []
which appears to be equivalent to just:
ENTRYPOINT ["node", "dist/main.js"]
you can't seem to use single quotes - double quotes are necessary, and you have to use shell syntax..not sure why, but this style does not work:
ENTRYPOINT node dist/main.js
Related
I'm stuck trying to achieve the objective described in the title. Tried various options last of which is found in this article. Currently my Dockerfile is as follows:
FROM ubuntu:18.04
EXPOSE 8081
CMD cd /var/www/html/components
CMD "bash myscript start" "-D" "FOREGROUND"
#ENTRYPOINT ["bash", "myscript", "start"]
Neither the CMD..."FOREGROUND" nor the commented-out ENTRYPOINT lines work. However, when I open an interactive shell into the container, cd into /var/.../components folder and execute the exact same command to run the script, it works.
What do I need to change?
Once you pass your .sh file, run it with CMD. This is a snippet:
ADD ./configure.and.run.myapp.sh /tmp/
RUN chmod +x /tmp/configure.and.run.myapp.sh
...
CMD ["sh", "-c", "/tmp/configure.and.run.myapp.sh"]
And here is my full dockerfile, have a look.
I see three problems with the Dockerfile you've shown.
There are multiple CMDs. A Docker container only runs one command (and then exits); if you have multiple CMD directives then only the last one has an effect. If you want to change directories, use the WORKDIR directive instead.
Nothing is COPYd into the image. Unless you explicitly COPY your script into the image, it won't be there when you go to run it.
The CMD has too many quotes. In particular, the quotes around "bash myscript start" make it into a single shell word, and so the system looks for an executable program named exactly that, including spaces as part of the filename.
You should be able to correct this to something more like:
FROM ubuntu:18.04
# Instead of `CMD cd`; a short path like /app is very common
WORKDIR /var/www/html/components
# Make sure the application is part of the image
COPY ./ ./
EXPOSE 8081
# If the script is executable and begins with #!/bin/sh then
# you don't need to explicitly say "bash"; you probably do need
# the path if it's not in /usr/local/bin or similar
CMD ./myscript start -D FOREGROUND
(I tend to avoid ENTRYPOINT here, for two main reasons. It's easier to docker run --rm -it your-image bash to get a debugging shell or run other one-off commands without an ENTRYPOINT, especially if the command requires arguments. There's also a useful pattern of using ENTRYPOINT to do first-time setup before running the CMD and this is a little easier to set up if CMD is already the main container command.)
Trying to run a pod based on an image with this Dockerfile:
...
ENTRYPOINT [ "./mybashscript", ";", "flask" ]
CMD [ "run" ]
I would be expecting the full command to be ./mybashscript; flask run.
However, in this example, the pod / container executes ./mybashscript but not flask.
I also tried a couple of variations like:
...
ENTRYPOINT [ "/bin/bash", "-c", "./mybashscript && flask" ]
CMD [ "run" ]
Now, flask gets executed but run is ignored.
PS: I am trying to understand why this doesn't work and I am aware that I can fit all into the entrypoint or shove everything inside the bash script, but that is not the point.
In both cases you show here, you use the JSON-array exec form for ENTRYPOINT and CMD. This means no shell is run, except in the second case where you run it explicitly. The two parts are just combined together into a single command.
The first construct runs the script ./mybashscript, which must be executable and have a valid "shebang" line (probably #!/bin/bash). The script is passed three arguments, which you can see in the shell variables $1, $2, and $3: a semicolon ;, flask, and run.
The second construct runs /bin/sh -c './mybashscript && flask' run. sh -c takes a single argument, which is mybashscript && flask; the remaining argument run is interpreted as a positional argument, and the sh -c command would see it as $0.
The arbitrary split of ENTRYPOINT and CMD you show doesn't really make sense. The only really important difference between the two is that it is easier to change CMD when you run the container, for example by putting it after the image name in a docker run command. It makes sense to put all of the command in the command part, or none of it, but not really to put half of the command in one part and half in another.
My first pass here would be to write:
# no ENTRYPOINT
CMD ./mybashscript && flask run
Docker will insert a sh -c wrapper for you in bare-string shell form, so the && has its usual Bourne-shell meaning.
This setup looks like you're trying to run an initialization script before the main container command. There's a reasonably standard pattern of using an ENTRYPOINT for this. Since it gets passed the CMD as parameters, the script can end with exec "$#" to run the CMD (potentially as overridden in the docker run command). The entrypoint script could look like
#!/bin/sh
# entrypoint.sh
./mybashscript
exec "$#"
(If you wrote mybashscript, you could also end it with the exec "$#" line, and use that script as the entrypoint.)
In the Dockerfile, set this wrapper script as the ENTRYPOINT, and then whatever the main command is as the CMD.
ENTRYPOINT ["./entrypoint.sh"] # must be a JSON array
CMD ["flask", "run"] # can be either form
If you provide an alternate command, it replaces CMD, and so the exec "$#" line will run that command instead of what's in the Dockerfile, but the ENTRYPOINT wrapper still runs.
# See the environment the wrapper sets up
docker run --rm your-image env
# Double-check the data directory setup
docker run --rm -v $PWD/data:/data your-image ls -l /data
If you really want to use the sh -c form and the split ENTRYPOINT, then the command inside sh -c has to read $# to find its positional arguments (the CMD), plus you need to know the first argument is $0 and not $1. The form you show would be functional if you wrote
# not really recommended but it would work
ENTRYPOINT ["/bin/sh", "-c", "./mybashscript && flask \"$#\"", "flask"]
CMD ["run"]
I am trying to deploy an app in payara micro based on payara dockerimage and I need to pass one arguement snapshotversion in ENTRYPOINT(basically i want to access the build args in ENTRYFORM) exec form, as exec form of ENTRYPOINT is preferred: my docker file is as follows:
FROM payara/micro:5.193.1
ARG snapshotversion
ENV snapshotvs=$snapshotversion
RUN jar xf payara-micro.jar
COPY /service/war/target/app-emailverification-service-war-${snapshotversion}.war ${DEPLOY_DIR}/
COPY ojdbc6.jar ${PAYARA_HOME}/
COPY --chown=payara domain.xml /opt/payara/MICRO-INF/domain/domain.xml
RUN cd /opt/payara/MICRO-INF/domain && ls -lrt
#ENTRYPOINT ["java", "-jar", "/opt/payara/payara-micro.jar", "--deploy", "/opt/payara/deployments/app-service-war-$snapshotvs.war", "--domainConfig", "/opt/payara/MICRO-INF/domain/domain.xml","--addLibs", "/opt/payara/ojdbc6.jar"]
ENTRYPOINT java -jar /opt/payara/payara-micro.jar --deploy /opt/payara/deployments/app-service-war-$snapshotvs.war --domainConfig /opt/payara/MICRO-INF/domain/domain.xml --addLibs /opt/payara/ojdbc6.jar
The commented ENTRYPOINT does not work. Container logs says invalid deployment. What am i missing here? Also how can I use CMD with this. Can someone post an example.
The commented line doesn't work, because it is an exec form of ENTRYPOINT, which doesn't invoke shell (/bin/sh -c), so variable substitution doesn't happening.
If you want to use an exec form and environment variables you need to specify it directly:
ENTRYPOINT ["sh", "-c", "your command with env variable"]
To your question about how can you use CMD with this, for example like this:
ENTRYPOINT ["sh", "-c"]
CMD ["your command with env variable"]
You mentioned, that you want to use build args in ENTRYPOINT instruction. It's not really possible, because nor ARG nor ENV are expanded in ENTRYPOINT or CMD: https://docs.docker.com/engine/reference/builder/#environment-replacement, https://docs.docker.com/engine/reference/builder/#scope
Also you could take a look at great page with best practices for writing Dockerfile and ENTRYPOINT instructions specifically.
Two suggestions that complement each other:
If you're COPYing a file into the image, you can give it a fixed name inside the image. That avoids this problem.
WORKDIR /opt/payara
COPY service/war/target/app-emailverification-service-war-${snapshotversion}.war deployments/app-service.war
If you have a particularly long or involved command that you're trying to make be the main container process, wrap it in a shell script. You want to make sure to exec the main container process to avoid some trouble around signal handling (resulting in docker stop pausing for 10 seconds and then hard-killing your actual process).
#!/bin/sh
exec java \
-jar /opt/payara/payara-micro.jar \
--deploy /opt/payara/deployments/app-service.war \
--domainConfig /opt/payara/MICRO-INF/domain/domain.xml \
--addLibs /opt/payara/ojdbc6.jar
COPY launch.sh ./
RUN chmod +x launch.sh
CMD ["/opt/payara/launch.sh"]
In this second case, it's a shell script, so you can have ordinary shell variable substitutions.
Is there a way to run a command on container stop either using the Dockerfile or in docker-compose.yml using Docker Compose? Essentially I want a command that runs on container exit (opposite to ENTRYPOINT).
Sure, ENTRYPOINT can do that. It takes the CMD as command-line arguments. Usually your ENTRYPOINT script will want to exec "$#" to run the CMD after doing its setup, but if you're willing to take on the responsibility of being process ID 1, you can run CMD as a subprocess and then do stuff afterwards.
#!/bin/sh
echo "BEFORE"
"$#"
STATUS=$?
echo "AFTER"
exit $STATUS
Note that the set of things you can usefully do at termination is pretty limited since your filesystem is about to go away.
Also note that this requires you to run your "normal" process as CMD, but for reasons like this I tend to think of that as better practice in any case. Your Dockerfile would look something like
...
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
CMD ["whateverd", "--foreground"]
As per Docker documentation:
There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.
I wish to execute a simple bash script(which processes docker environment variable) before the CMD command(which is init in my case).
Is there any way to do this?
Use a custom entrypoint
Make a custom entrypoint which does what you want, and then exec's your CMD at the end.
NOTE: if your image already defines a custom entrypoint, you may need to extend it rather than replace it, or you may change behavior you need.
entrypoint.sh:
#!/bin/sh
## Do whatever you need with env vars here ...
# Hand off to the CMD
exec "$#"
Dockerfile:
COPY entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Docker will run your entrypoint, using CMD as arguments. If your CMD is init, then:
/entrypoint.sh init
The exec at the end of the entrypoint script takes care of handing off to CMD when the entrypoint is done with what it needed to do.
Why this works
The use of ENTRYPOINT and CMD frequently confuses people new to Docker. In comments, you expressed confusion about it. Here is how it works and why.
The ENTRYPOINT is the initial thing run inside the container. It takes the CMD as an argument list. Therefore, in this example, what is run in the container is this argument list:
# ENTRYPOINT = /entrypoint.sh
# CMD = init
["/entrypoint.sh", "init"]
# or shown in a simpler form:
/entrypoint.sh init
It is not required that an image have an ENTRYPOINT. If you don't define one, Docker has a default: /bin/sh -c.
So with your original situation, no ENTRYPOINT, and using a CMD of init, Docker would have run this:
/bin/sh -c 'init'
^--------^ ^--^
| \------- CMD
\--------------- ENTRYPOINT
In the beginning, Docker offered only CMD, and /bin/sh -c was hard-coded as the ENTRYPOINT (you could not change it). At some point along the way, people had use cases where they had to do more custom things, and Docker exposed ENTRYPOINT so you could change it to anything you want.
In the example I show above, the ENTRYPOINT is replaced with a custom script. (Though it is still ultimately being run by sh, because it starts with #!/bin/sh.)
That ENTRYPOINT takes the CMD as is argument. At the end of the entrypoint.sh script is exec "$#". Since $# expands to the list of arguments given to the script, this is turned into
exec "init"
And therefore, when the script is finished, it goes away and is replaced by init as PID 1. (That's what exec does - it replaces the current process with a different command.)
How to include CMD
In the comments, you asked about adding CMD in the Dockerfile. Yes, you can do that.
Dockerfile:
CMD ["init"]
Or if there is more to your command, e.g. arguments like init -a -b, would look like this:
CMD ["init", "-a", "-b"]
Dan's answer was correct, but I found it rather confusing to implement. For those in the same situation, here are code examples of how I implemented his explanation of the use of ENTRYPOINT instead of CMD.
Here are the last few lines in my Dockerfile:
#change directory where the mergeandlaunch script is located.
WORKDIR /home/connextcms
ENTRYPOINT ["./mergeandlaunch", "node", "keystone.js"]
Here are the contents of the mergeandlaunch bash shell script:
#!/bin/bash
#This script should be edited to execute any merge scripts needed to
#merge plugins and theme files before starting ConnextCMS/KeystoneJS.
echo Running mergeandlaunch script
#Execute merge scripts. Put in path to each merge script you want to run here.
cd ~/theme/rtb4/
./merge-plugin
#Launch KeystoneJS and ConnextCMS
cd ~/myCMS
exec "$#"
Here is how the code gets executed:
The ENTRYPOINT command kicks off the mergeandlaunch shell script
The two arguments 'node' and 'keystone.js' are passed along to the shell script.
At the end of the script, the arguments are passed on to the exec command.
The exec command then launched my node program the same way the Docker command CMD would do.
Thanks to Dan for his answer.
Although I found I had to do something like this within the Dockerfile:
WORKDIR /
COPY startup.sh /
RUN chmod 755 /startup.sh
ENTRYPOINT sh /startup.sh /usr/sbin/init
NOTE: I named the script startup.sh as opposed to entrypoint.sh
The key here was that I needed to provide 'sh' otherwise I kept getting "no such file..." errors coming out of 'docker logs -f container_name'.
See:
https://github.com/docker/compose/issues/3876