I have script.sh
as below:
MYVAR=world
I then have a Dockerfile
as below:
FROM redhat/ubi8
COPY script.sh /script.sh
# Source the script and echo. Expected to see: hello world
RUN source /script.sh && echo hello $MYVAR
# Run echo without sourcing script. Expected to see: hello
RUN echo hello $MYVAR
# Modify SHELL and echo. Expected to see: hello world
SHELL ["/bin/bash", "-c", "'source /script.sh'", "&&"]
RUN echo hello $MYVAR
The final SHELL
and RUN
command does not work as expected. My expectation is that ultimately, the final RUN
command should be /bin/bash -c 'source /script.sh' && echo hello $MYVAR
. Running docker build . --preview plain
does indeed indicate that’s the command. And, if I run that command on my own command line, it works.
But when I build I get the following error:
#9 [5/5] RUN echo hello $MYVAR
#9 0.194 &&: source /script.sh: No such file or directory
#9 ERROR: process "/bin/bash -c 'source /script.sh' && echo hello $MYVAR" did not complete successfully: exit code: 127
------
> [5/5] RUN echo hello $MYVAR:
0.194 &&: source /script.sh: No such file or directory
------
Dockerfile:12
--------------------
10 | SHELL ["/bin/bash", "-c", "'source /script.sh'", "&&"]
11 |
12 | >>> RUN echo hello $MYVAR
13 |
--------------------
ERROR: failed to solve: process "/bin/bash -c 'source /script.sh' && echo hello $MYVAR" did not complete successfully: exit code: 127
Why does this SHELL
and RUN
combination fail to work in my Dockerfile? I’ve tried other alternatives for the SHELL
command, including the following, but none have worked. Most have errored with varying exit codes.
This one does not have an exit code, but the echo doesn’t echo anything:
SHELL ["/bin/bash", "-c", "source /script.sh", "&&"]
SHELL ["/bin/bash", "-c", ""source /script.sh"", "&&"]
SHELL ["/bin/bash", "-c", "source /script.sh &&"]
SHELL ["/bin/bash", "-c", "'source /script.sh' &&"]
SHELL ["/bin/bash", "-c", ""source /script.sh" &&"]
The following questions are related, but not exact:
2
Answers
The
&&
and executing multiple commands is a shell syntax not recognized by the kernel’s exec syscall. You’ve also single quoted the'source /script.sh'
command that forces/bin/bash
to attempt to execute that single command, rather than sourcing/script.sh
.Even if this did work, the next problem you’d have is that
source
only applies to the single bash shell instance, and all variables set will no longer be defined when that shell exits and the next command is executed, which means/bin/bash -c "source /script.sh"
is mostly a no-op. If you ran that right now from the prompt you’d see that none of the variables exported from that script are defined.I’d find it easiest to maintain a Dockerfile with chained commands to run in a single run line, like:
Under the hood, a Unix command is a series of words, and low-level system calls like execve(2) take the series of words. If you use any of the Dockerfile "exec format" JSON-syntax directives (like
RUN ["echo", "hello"]
) you explicitly specify the sequence of words; the only quoting and escaping that applies is the JSON string quoting.The default Docker shell setting is
SHELL ["/bin/sh", "-c"]
. If you have a "shell" format command likeRUN echo hello $MYVAR
, internally that string is passed as a single additional argument to theSHELL
.The last part of this riddle is how
sh -c
actually works. It takes the next single word from the command line, and runs it as a shell command. If that single-word command string happens to contain positional parameter references like$0
,$1
, … then those refer to any additional arguments after the command string.So in your setup, when you say
The list of command words is (one word to a line, all punctuation included as part of the argument)
This isn’t what you want, in several ways. The single-quoted
'source /script.sh'
is interpreted as a single word because the shell sees the single quotes.&&
is passed as a separate parameter and not a shell operator, and both that and the actual command are in positional parameters and not part of the command string.If you’re aware of this syntax you can sort of make it work
Note that I’ve used the standard
.
instead ofsource
, I’ve filled in a command name to be$0
, and I’m explicitly using theRUN
string$1
inside the command string.For what you’ve shown in the question, to just set a single environment variable, it’s far simpler to use Docker primitives
and leave
SHELL
alone. For some specific things like Python virtual environments, you can e.g. just prepend the virtual environment directory to$PATH
instead of trying to run theactivate
script.If the script really truly is dynamic, there’s a simpler pattern of using the Dockerfile
ENTRYPOINT
to first read the script and then run the main containerCMD
. In DockerfileRUN
instructions you’d need to explicitly read in the script, as shown in @BMitch’s answer. This is probably simpler and clearer than trying to manipulateSHELL
this way.