6

I am trying to execute a script during container startup.

For a normal linux machine, if i have script in /etc/rc.local script will be executed when there is reboot

I am trying to do the same thing, when building Docker image. I override the /etc/rc.local file with new content, then I perform

sudo docker run -d <image id>

I was expecting /etc/rc.local would be executed, however nothing happens.

Am I doing it in the wrong place?

techraf
  • 4,952
jojo
  • 463

2 Answers2

4

Docker only runs the process (or processes) that you tell it to run.

Docker containers normally don't have a functioning init system. They are not like virtual machines that contain a complete operating system that executes all the initialisation when it is booted.

If you want /etc/rc.local to run, you have to start it yourself. Something like:

docker run image /bin/bash -c "/etc/rc.local; <your command>"

There are some alternative base images that support running services on start-up, e.g. phusion/baseimage-docker.

Read this article to understand what the problems and issues are with Docker and starting a container.

NZD
  • 2,818
3

docker-compose, run a script after container has started?

COPY <<'DASH' /etc/rc.local
    set -x
    printenv
DASH

ENTRYPOINT ["dash", "-xc", ". /etc/rc.local && exec <the original entrypoint> &quot;$@&quot;", "$@"] CMD [<the original cmd in exec form>]

Explain:

  1. The file /etc/rc.local is a historical filename for putting scripts that will be executed by pre-systemd-era-daemon SysV init during the system boot.

    Another similar path for this purpose is /etc/init.d/*.

    Here we just take this filename for convention as in docker container there's no init/systemd daemon by default and the ENTRYPOINT is the pid 1.

  2. The original value of image ENTRYPOINT can be found in its Dockerfile or get overrided by compose.yaml.

  3. And setting a new ENTRYPOINT will reset the original CMD to empty string:

    If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value.

    so we have to copy the value of CMD from the Dockerfile of original image or compose.yaml if get overrided in it.

  4. sh -xc 'echo "$@"' 1 2 3 is a way to pass shell arguments into sh -c, and this example shall run echo 1 2 3 that can be verified by set -x.

  5. dash is yet another implement of shell that's faster than bash and being used as the default /bin/sh in Debian.

    If you use some bashism features that in /etc/rc.local, feel free to replace it with bash or other shell implements.

  6. $@ is the value of all shell arguments except the first one like $argv[0] or $0 which is the value being passed to execv.

    In the shell env of ENTRYPOINT when a container is created, its $@ will be the value of Dockerfile CMD, so we could pass the value CMD from outer shell into the inner that created by sh -c 'echo "$@"' $@.

  7. The value of ENTRYPOINT and CMD in Dockerfile or compose.yaml must be written in exec form for removing the extra ["sh", "-c"] being prepend to the value when using shell form.

    • Using
      docker image inspect <compose project name>-<compose service name> \
      | jq '.[0].Config | with_entries(select([.key] | inside(["Cmd", "Entrypoint"])))'
      
      to view the value of ENTRYPOINT or CMD of a built image for compose service that can be found in docker images -a.
    • Whereas the <compose project name> should be the value of $COMPOSE_PROJECT_NAME that defaults the dirname(1) of the path of compose.yaml.
    • The jq expression just like _.pick() in lodash.
  8. Double-quoting $@ as "$@" will prevent shell IFS= word splitting for passing the original Dockerfile CMD as is into $1 of sh -c.

    This can be verified by for word in "$@"; do echo "$word"; done in /etc/rc.local "$@" and can fix some issues like nginx: invalid option: "off" with the offical docker image nginx.

  9. Prepend exec(1p) before "$@" will replace the ENTRYPOINT process dash with the first one in $@ array.

    This is a common pattern with docker entrypoint to allow passing UNIX signal to the proper process as only the topmost init process, that either to be the ENTRYPOINT or be replaced by exec in ENTRYPOINT, can recive UNIX signal from docker daemon. Or you will have to write a signal proccesser with trap in the entrypoint shell or with signal() in the entrypoint process.

    Most ENTRYPOINT shell in well-formed Dockerfile has already an exec "$@" in the end so the pid 1 will be replaced by twice, and this exec can still act as a safety net.

  10. The dot . before /etc/rc.local is the what source alias to in bashism. Comparing to execute the /etc/rc.local directly without prepending ., sourcing it won't require chmod +x and can pass exported enviornment variables into the dash as entrypoint.


Taking the offical docker image php as a example: We can find its original ENTRYPOINT is docker-php-entrypoint and original CMD is php-fpm, so we should fill them with:

ENTRYPOINT ["dash", "-xc", ". /etc/rc.local && exec docker-php-entrypoint \"$@\"", "$@"]
CMD ["php-fpm"]

If the order of executing script before or after the entrypoint get started is not important for you, also try the much simpler post-start lifecycle hook in Docker Compose.

n0099
  • 131