0

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,

  • I'd guess `docker ps` is inserting the quotes. Since you're using the [exec form](https://docs.docker.com/engine/reference/builder/#exec-form-entrypoint-example) of `ENTRYPOINT`, you're telling Docker you want to pass the literal string `<(...)` and not have a shell interpret it. What should this construct actually do; does the Dockerfile include a `CMD`; and might there be a simpler way to accomplish it? – David Maze Jan 31 '23 at 11:07
  • @DavidMaze Yeah, I can add a script file to source the other script file and use it instead of the <(echo...) business. When I try the quoted command, it fails, but if I remove the quotes it works. This is what leads me to believe docker is inserting the quotes. The scripttosource.sh sets some environment variables. – Kevin Stallard Jan 31 '23 at 11:18
  • Also, if you look at the second argument to ENTRYPOINT (from the docker ps output) it does not add quotes. – Kevin Stallard Jan 31 '23 at 11:24
  • Can you use Dockerfile `ENV` instead of the script? Or just pass the filename directly to the `bash --rcfile` option? Or write an entrypoint wrapper script that doesn't specifically need bash? [Using the RUN instruction in a Dockerfile with 'source' does not work](https://stackoverflow.com/questions/20635472/using-the-run-instruction-in-a-dockerfile-with-source-does-not-work) could give some inspiration, though its answers are many and varied. – David Maze Jan 31 '23 at 11:46
  • Yes, there are certainly many ways to do this. I am just curious about this apparent behavior of docker. I'd like to understand it a bit better. – Kevin Stallard Jan 31 '23 at 12:04

2 Answers2

2

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 also CMD, and less frequently RUN) 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 to bash --rcfile, and nothing actually executes it.

The obvious answer here is to use the string-syntax shell form instead

ENTRYPOINT /bin/bash -rcfile <(echo '. ./mydir/scripttosource.sh')

Docker wraps this in an invocation of sh -c (or the Dockerfile SHELL). That causes a shell to preprocess the command string, break it into words, and execute it. Assuming the SHELL 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 the CMD. 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 string something and you can do it without the substitution. If you can avoid the substitution then you don't need the shell either:

ENTRYPOINT ["/bin/bash", "--rcfile", "./mydir/scripttosource.sh"]

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, then exec "$@" to run the main container command. In particular, if you use the shell . command to set environment variables (equivalent to the bash-specific source) those will "stick" for the main container process.

#!/bin/sh
# entrypoint.sh

# read the file that sets variables
. ./mydir/scripttosource.sh

# run the main container command
exec "$@"
# Dockerfile
COPY entrypoint.sh ./           # may be part of some other COPY
ENTRYPOINT ["./entrypoint.sh"]  # must be JSON-array syntax
CMD ???

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.

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • What you are saying in a nutshell is that the <(echo...) part isn't actually being executed. There really isn't a problem with single quotes, it is actually that ENTRYPOINT and its arguments are not acting like a normal shell. Things that require some level of execution to come up with a result are not going to run if made as a direct argument to ENTRYPOINT. – Kevin Stallard Jan 31 '23 at 14:02
  • Is it possible that this is why docker puts the <(echo) in single quotes in the docker ps output, because it is telling me that it is being interpreted literally? Docker does so many magic things that I just assumed it would be processed like a shell command.....thanks for the clarifications – Kevin Stallard Jan 31 '23 at 14:08
0

insertion of single quotes can be avoided by using escape characters in the third argument to ENTRYPOINT.

ENTRYPOINT ["/bin/bash", "-rcfile","$(echo '. ./mydir/scripttosource.sh')"]
Raushan Kumar
  • 1,195
  • 12
  • 21
  • This also doesn't work, the result of this change results in: ```/bin/bash -rcfile '$(echo '. ./mydir/scripttosource.sh')'``` Still inserting single quotes and this version of the command even without single quotes results in "Script is beign run, should be sourced". – Kevin Stallard Jan 31 '23 at 11:02
  • use double quotes ENTRYPOINT ["/bin/bash", "-rcfile", "$(echo '. ./mydir/scripttosource.sh')"] – Raushan Kumar Jan 31 '23 at 11:31
  • I am using double quotes.... – Kevin Stallard Jan 31 '23 at 11:35