How to write a Dockerfile which I can start a service and run a shell and also accept arguments for the shell?

Freewind picture Freewind · Jan 9, 2016 · Viewed 10.3k times · Source

In a Dockerfile, the latest instruction is:

CMD  sudo chown -R user:user /home/user/che && \
     sudo service docker start && \
     cd /home/user/che/bin/ && ./che.sh run

It works but I can't pass more arguments to ./che.sh.

The che.sh checks if the internal docker is started after doing other tasks. And it can accept several optional arguments, like -r:111.111.111.111.

I tried to modify the instruction as:

RUN sudo chown -R user:user /home/user/che && \
     sudo service docker start
ENTRYPOINT ["/home/user/che/bin/che.sh"]

in order to invoke it like docker run -it --priviledged my/che -r:111.111.111.111 run, but the che.sh shell will report internal docker is not working well.

I also tried:

ENTRYPOINT ["sudo service docker start", "&&", "/home/user/che/bin/che.sh run"]

even:

ENTRYPOINT ["sh", "-c" "sudo service docker start && /home/user/che/bin/che.sh run"]

But it will report sudo service docker start is not found in $PATH, or the che.sh doesn't run.

What's the correct way to write it?

  1. sudo service docker start should run when che.sh is invoked
  2. I need to pass arguments from outside to che.sh, like docker run -it --priviledged my/che -r:111.111.111.111 run

Answer

Lanti picture Lanti · Jan 9, 2016

You have to use supervisord inside a Docker container able to use more complex shell syntax when you creating containers.

Docker documentation about supervisord: https://docs.docker.com/engine/articles/using_supervisord/

YOU CAN use more complex shell syntax (that you want to use) when you create a new container with $ docker run command, however this will not work within systemd service files (due to limitation in systemd) and docker-compose .yml files and the Dockerfiles also.

First, you have to install supervisord in your Dockerfile:

RUN apt-get -y update && apt-get -y dist-upgrade \
    && apt-get -y install \
        supervisor
RUN mkdir -p /var/log/supervisord

Than place this at the end of the Dockerfile:

COPY etc/supervisor/conf.d/supervisord.conf /etc/supervisor/conf.d/
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

Create a file in etc/supervisor/conf.d/supervisord.conf next to your Dockerfile:

[unix_http_server]
file=/var/run/supervisord.sock
chmod=0777
chown=root:root
username=root

[supervisord]
nodaemon=true
user=root
environment=HOME="/root",USER="root"
logfile=/var/log/supervisord/supervisord.log
pidfile=/var/run/supervisord.pid
childlogdir=/var/log/supervisord
logfile_maxbytes=10MB
loglevel=info

[program:keepalive]
command=/bin/bash -c 'echo Keep Alive service started... && tail -f /dev/null'
autostart=true
autorestart=true
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/var/log/supervisord/keepalive-stdout.log
stdout_logfile_maxbytes=1MB
stderr_logfile=/var/log/supervisord/keepalive-stderr.log
stderr_logfile_maxbytes=1MB

[program:dcheck]
command=/bin/bash -c 'chmod +x /root/dcheck/repo/dcheck.sh && cd /root/dcheck/repo && ./dcheck.sh'
autostart=true
autorestart=true
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/var/log/supervisord/dcheck-stdout.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/log/supervisord/dcheck-stderr.log
stderr_logfile_maxbytes=1MB

This is a more complex supervisord.conf and probably you don't need many of the commands here, plus you have to change the file locations to your needs. However you can see how to create log files from the bash output of the script.

Later on you have to docker exec in that container and you can watch real-time the log with:

docker exec -it your_running_container /bin/bash -c 'tail -f /var/log/supervisord/dcheck-stdout.log'

You have the option to show subprocess log in the main supervisord log with loglevel=debug, however this is full of timestamps and comments, not the pure bash output like when you run the script directly.

As you can see in my scipt, I keeping alive the container with tail -f /dev/null, however this is a bad practice. The .sh script should keep alive your container on their own.

When you sending your scipt to ENTRYPOINT as ENTRYPOINT ["sudo service docker start", "&&", "/home/user/che/bin/che.sh run"], you want to change the default docker ENTRYPOINT from /bin/sh -c to sudo (also, use full location names).

There are two ways to change docker ENTRYPOINT in Dockerfile. One is to place this in the head section of your Dockerfile:

RUN ln -sf /bin/bash /bin/sh && ln -sf /bin/bash /bin/sh.distrib

Or place this at the bottom:

ENTRYPOINT ['/bin/bash', '-c']

After when you send any CMD to this Dockerfile, it will be run by /bin/bash -c command.

One more thing to note is that the first command takes PID1, so if you want to run the .sh script without tail -f /dev/null in my supervisord script, it will take PID1 process place and CTRL+C command will not gonna work. You have to shut down the container from another shell instance.

But if you run the command with:

[program:dcheck]
command=/bin/bash -c 'echo pid1 > /dev/null && chmod +x /root/dcheck/repo/dcheck.sh && cd /root/dcheck/repo && ./dcheck.sh'

echo pid1 > /dev/null will take PID1 and SIGTERM, SIGKILL and SIGINT will work again with your shell script.

I try to stay away running Docker with --privileged flag. You have many more options to get away on the limitations.

I don't know anything about your stack, but generally good idea to not dockerise Docker in a Container. Is there a specific reason why sudo service docker start is in your Dockerfile?

I don't know anything about this container, is it have to be alive? Because if doesn't, there is a more simple solution, only running the container when it has to process something from the command line. Place this file on the host with the name of run let's say in /home/hostuser folder and give it chmod +x run:

#!/bin/bash
docker run --rm -it -v /home/hostuser/your_host_shared_folder/:/root/your_container_shared_folder/:rw your_docker_image "echo pid1 > /dev/null && chmod +x /root/script.sh && cd  /root && ./script.sh"

In this case, ENTRYPOINT is preferred to be ENTRYPOINT ['/bin/bash', '-c'].

Run this script on the host with:

$ cd /home/hostuser
$ ./run -flag1 -flag2 args1 args2 args3