Execute a script before CMD

2019-01-23 15:20发布

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?

3条回答
Ridiculous、
2楼-- · 2019-01-23 15:44

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"]
查看更多
贪生不怕死
3楼-- · 2019-01-23 15:56

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

查看更多
聊天终结者
4楼-- · 2019-01-23 15:57

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:

  1. The ENTRYPOINT command kicks off the mergeandlaunch shell script
  2. The two arguments 'node' and 'keystone.js' are passed along to the shell script.
  3. At the end of the script, the arguments are passed on to the exec command.
  4. The exec command then launched my node program the same way the Docker command CMD would do.
查看更多
登录 后发表回答