I am creating a Dockerfile that needs to source a script before a shell is run.
ENTRYPOINT ["/bin/bash", "-rcfile","<(echo '. ./mydir/scripttosource.sh')"]
However, the script isn’t sourced as expected.
Combining these parameters on a command line (normal Linux instance, outside of any Docker container), it works properly, for example:
$ /bin/bash -rcfile <(echo '. ./mydir/scripttosource.sh')
So I took a look at what was actually used by the container when it was run.
$ docker ps --format "table {{.ID}} t {{.Names}} t {{.Command}}" --no-trunc
CONTAINER ID NAMES COMMAND
70a5f846787075bd9bd55432dc17366268c33c1ab06fb36b23a50f5c3aef19bb happy_cray "/bin/bash -rcfile '<(echo '. ./mydir/scripttosource.sh')'"
Besides the fact that it properly identified the emotional state of Cray computers, Docker seems to be sneaking in undesired single quotes into the third parameter to ENTRYPOINT.
'<(echo '. ./mydir/scripttosource.sh')'
Thus the command actually being executed is:
$ /bin/bash -rcfile '<(echo '. ./mydir/scripttosource.sh')'
Which doesn’t work…
Now I realize there are more ways to skin this cat, and I could make this work a different way, I am curious about the insertion of single quotes to the third argument to ENTRYPOINT. Is there a way to avoid this?
Thank you,
2
Answers
insertion of single quotes can be avoided by using escape characters in the third argument to ENTRYPOINT.
At a super low level, the Unix execve(2) function launches a process by taking a sequence of words, where the first word is the actual command to run and the remaining words are its arguments. When you run a command interactively, the shell breaks it into words, usually at spaces, and then calls an exec-type function to run it. The shell also does other processing like replacing
$VARIABLE
references or the bash-specific<(subprocess)
construct; all of these are at layers above simply "run a process".The Dockerfile
ENTRYPOINT
(and alsoCMD
, and less frequentlyRUN
) has two forms. You’re using the JSON-array exec form. If you do this, you’re telling Docker that you want to run the main container command with exactly these three literal strings as arguments. In particular the<(...)
string is passed as a literal argument tobash --rcfile
, and nothing actually executes it.The obvious answer here is to use the string-syntax shell form instead
Docker wraps this in an invocation of
sh -c
(or the DockerfileSHELL
). That causes a shell to preprocess the command string, break it into words, and execute it. Assuming theSHELL
is bash and not a pure POSIX shell, this will handle the substitution.However, there are some downsides to this, most notably that the
sh -c
invocation "eats" all of the arguments that might be passed in theCMD
. If you want your main container process to be anything other than an interactive shell, this won’t work.This brings you to the point of trying to find simpler alternatives to doing this. One specific observation is that the substitution here isn’t doing anything;
<(echo something)
will always produce the fixed stringsomething
and you can do it without the substitution. If you can avoid the substitution then you don’t need the shell either:Another sensible approach here is to use an entrypoint wrapper script. This uses the
ENTRYPOINT
to run a shell script that does whatever initialization is needed, thenexec "$@"
to run the main container command. In particular, if you use the shell.
command to set environment variables (equivalent to the bash-specificsource
) those will "stick" for the main container process.This should have the same net effect. If you get a debugging shell with
docker run --rm -it your-image bash
, it will run under the entrypoint wrapper and see the environment variables. You can do other setup in the wrapper script if required. This particular setup also doesn’t use any bash-specific options, and might run better under minimal Alpine-based images.