skip to Main Content

I have an executable C++ application which can only be run and used after sourcing a bash
script in order to set up the shell environment (only Linux systems):

$ . setupenv.sh
$ my_app

Normally, the setupenv.sh script is sourced in the user’s .bashrc or .profile so that they can run the app from the command line.

For a few years I have been generating docker images for my app (as part of CI) with a
Dockerfile which ends with

SHELL ["/bin/bash", "-c"]

CMD source setupenv.sh && my_app

which is OK but I want to be able to do

(1) $ docker run my_docker
(2) $ docker run my_docker bash
(3) $ docker run my_docker [some arguments to be passed to my_app]

With the solution presented above, only (1) works.
(2) launches a bash shell in the container, but the environment is not set: I would want the environment to be set up in this case i.e. have setupenv.sh be executed.
(3) just doesn’t work at all.

I have read and re-read the doc and searched everywhere for answers, I sort of understand that some kind of ENTRYPOINT/CMD combination is required, but I cannot get this to work.

Can anybody help please?

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to the suggestions of @david-maze above, I have got this to work the way I wanted. There are some slight modifications compared to the initial suggestions:

    • in my Dockerfile, I have
    WORKDIR /
    COPY entrypoint.sh entrypoint.sh
    

    therefore I had to modify the ENTRYPOINT command to

    ENTRYPOINT ["/entrypoint.sh"]
    

    in case the container is run with e.g. -w $HOME (after of course mounting the $HOME disk inside the container);

    • my setupenv.sh script is strongly bash-dependent, therefore I had to use
    #!/bin/bash
    

    in the entrypoint.sh otherwise the syntax is not understood by /bin/sh.

    Finally, in order to treat all 3 of my use cases above, including in fact the 2 versions of case (3) which are (i) passing an argument of type '--option' or (ii) passing an argument which is the name of a data file to open or a macro file to execute, I modified the suggestion of @david-maze like so:

    case "$1" in
    
      -*)
        exec my_app "$@"
      ;;
      
      *)
       if [ -x "$1" ]; then
          exec "$@"
       else
          if [ -x "$(which $1)" ]; then
             exec "$@"
          else
             exec my_app "$@"
          fi
       fi
      ;;
      
    esac
    

    Now I have exactly the behaviour I wanted:

    (1) $ docker run my_docker      => run app
    (2) $ docker run my_docker some_exec => run shell or other executable with environment set up correctly
    (3) $ docker run my_docker --some-option => run app with given option
    (3+)$ docker run my_docker some_file.dat => open file in app
    

    Thank you!


  2. This is a pretty typical use of an entrypoint wrapper script. The basic trick here is that you can specify both an ENTRYPOINT and a CMD, and it’s easier to override the CMD. Only the ENTRYPOINT runs but it gets passed the (possibly overridden) CMD as arguments. The (POSIX) shell command exec "$@" will replace the current process with the command line from the arguments.

    So your basic script could look like

    #!/bin/sh
    # (prefer a plain POSIX shell if possible)
    
    # Read in the environment file (avoid bash-specific `source`)
    . ./setupenv.sh
    
    # Switch to the main container process
    exec "$@"
    

    In your Dockerfile, make this script be the ENTRYPOINT, make the actual thing you want to run be the CMD, and do not set SHELL.

    ENTRYPOINT ["./entrypoint.sh"]  # must be JSON-array syntax
    CMD ["my_app"]                  # can be shell syntax also
    

    This should cover your first two cases. If you want to pass additional arguments then the easiest way is to repeat the command.

    docker run -d my_image
    docker run --rm my_image env
    docker run --rm -it my_image bash
    docker run -d my_image my_app --with-options
    

    The script can do whatever it likes, including examining the command before it runs it. The node image of note tries to run a command as a Node script if it doesn’t resolve as a command, though this can be confusing. If you want to support passing options without a command, a simpler approach could be to see if it looks like the command starts with an option; for example

    case "$1" of
      -*) exec my_app "$@" ;;
      *) exec "$@" ;;
    esac
    
    docker run -d my_image --with-options
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search