66

How can I include my own shell script CMD on container start/restart/attach, without removing the CMD used by an inherited image?

I am using this, which does execute my script fine, but appears to overwrite the PHP CMD:

FROM php

COPY start.sh /usr/local/bin

CMD ["/usr/local/bin/start.sh"]

What should I do differently? I am avoiding the prospect of copy/pasting the ENTRYPOINT or CMD of the parent image, and maybe that's not a good approach.

Johnathan Elmore
  • 2,156
  • 1
  • 19
  • 25

3 Answers3

52

As mentioned in the comments, there's no built-in solution to this. From the Dockerfile, you can't see the value of the current CMD or ENTRYPOINT. Having a run-parts solution is nice if you control the upstream base image and include this code there, allowing downstream components to make their changes. But docker there's one inherent issue that will cause problems with this, containers should only run a single command that needs to run in the foreground. So if the upstream image kicks off, it would stay running without giving your later steps a chance to run, so you're left with complexities to determine the order to run commands to ensure that a single command does eventually run without exiting.

My personal preference is a much simpler and hardcoded option, to add my own command or entrypoint, and make the last step of my command to exec the upstream command. You will still need to manually identify the script name to call from the upstream Dockerfile. But now in your start.sh, you would have:

#!/bin/sh

# run various pieces of initialization code here
# ...

# kick off the upstream command:
exec /upstream-entrypoint.sh "$@"

By using an exec call, you transfer pid 1 to the upstream entrypoint so that signals get handled correctly. And the trailing "$@" passes through any command line arguments. You can use set to adjust the value of $@ if there are some args you want to process and extract in your own start.sh script.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • 2
    It's important to note that if you're inheriting from an image that defines both `ENTRYPOINT` and `CMD` that you might need to define arguments in order for the `exec` command to work. I had a case where the upstream entrypoint required at least one argument or it would terminate with no error and no output. – ngreen Feb 27 '18 at 03:15
  • 7
    I'm still surprised that there isn't a builtin option or easy method for capturing the parent image's CMD when you extend it in a Dockerfile. I didn't accept this answer because I thought for sure there was going to be a feature or a good method for this, but after two years I have a feeling I'm still misunderstanding some core concept. – Johnathan Elmore May 31 '19 at 15:40
  • 7
    As of this comment, specifying ENTRYPOINT in a child Dockerfile will wipe out the parent's CMD. You'll need to specify the parent's CMD after your ENTRYPOINT. – JonShipman Aug 08 '19 at 19:13
  • 3
    @JonShipman That's a known and expected behavior: https://stackoverflow.com/a/47063296/596285 – BMitch Aug 08 '19 at 19:32
10

If the base image is not yours, you unfortunately have to call the parent command manually.

If you own the parent image, you can try what the people at camptocamp suggest here.

They basically use a generic script as an entry point that calls run-parts on a directory. What that does is run all scripts in that directory in lexicographic order. So when you extend an image, you just have to put your new scripts in that same folder.

However, that means you'll have to maintain order by prefixing your scripts which could potentially get out of hand. (Imagine the parent image decides to add a new script later...).

Anyway, that could work.

Update #1

There is a long discussion on this docker compose issue about provisioning after container run. One suggestion is to wrap you docker run or compose command in a shell script and then run docker exec on your other commands.

If you'd like to use that approach, you basically keep the parent CMD as the run command and you place yours as a docker exec after your docker run.

Morgan Kobeissi
  • 423
  • 1
  • 5
  • 11
  • 2
    broken link (camptocamp) – danfromisrael Jun 22 '20 at 11:18
  • This seems to be the best answer currently. I don't think docker-compose is the place to fix this - not before a Dockerfile can refer to its inherited ENTRYPOINT and CMD, so it has a way to run some initialization code without relying on hardcoding those into the Dockerfile. – reinierpost Jul 02 '21 at 12:43
4

Using mysql image as an example

Do docker inspect mysql/mysql-server:5.7 and see that:

  • Config.Cmd="mysqld"
  • Config.Entrypoint="/entrypoint.sh"

which we put in bootstrap.sh (remember to chmod a+x):

#!/bin/bash

echo $HOSTNAME
echo "Start my initialization script..."

# docker inspect results used here
/entrypoint.sh mysqld

Dockerfile is now:

FROM mysql/mysql-server:5.7
# put our script inside the image
ADD bootstrap.sh /etc/bootstrap.sh
# set to run our script
ENTRYPOINT ["/bin/sh","-c"]
CMD ["/etc/bootstrap.sh"]

Build and run our new image:

  • docker build --rm -t sidazhou/tmp-mysql:5.7 .
  • docker run -it --rm sidazhou/tmp-mysql:5.7

Outputs:

6f5be7c6d587
Start my initialization script...
[Entrypoint] MySQL Docker Image 5.7.28-1.1.13
[Entrypoint] No password option specified for new database.
...
...

You'll see this has the same output as the original image:

docker run -it --rm mysql/mysql-server:5.7

[Entrypoint] MySQL Docker Image 5.7.28-1.1.13
[Entrypoint] No password option specified for new database.
...
...
Alexandre Hamez
  • 7,725
  • 2
  • 28
  • 39
Sida Zhou
  • 3,529
  • 2
  • 33
  • 48
  • Everything is coupled in an undesired way, but it's much clearer what's going on. The big plus is that you don't need to know the Dockerfile of the image, which might be unavailable to you. – Sida Zhou Jul 02 '21 at 08:32
  • If we want to run scripts after `mysqld`, like populating db, then we do this https://stackoverflow.com/questions/44140593/how-to-run-command-after-initialization – Sida Zhou Jul 02 '21 at 09:12