skip to Main Content

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


  1. 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:

    FROM redhat/ubi8
    COPY script.sh /script.sh
    
    RUN source /script.sh 
     && echo hello $MYVAR 
     && ... more install commands here ...
    
    Login or Signup to reply.
  2. 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 like RUN echo hello $MYVAR, internally that string is passed as a single additional argument to the SHELL.

    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

    SHELL ["/bin/bash", "-c", "'source /script.sh'", "&&"]
    RUN echo hello $MYVAR
    

    The list of command words is (one word to a line, all punctuation included as part of the argument)

    /bin/bash
    -c
    'source /script.sh'
    &&
    echo hello $MYVAR
    

    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

    SHELL ["/bin/sh", "-c", ". /script.sh && eval "$1"", "sh"]
    RUN echo hello $MYVAR
    

    Note that I’ve used the standard . instead of source, I’ve filled in a command name to be $0, and I’m explicitly using the RUN 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

    ENV MYVAR=world
    RUN echo hello $MYVAR
    

    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 the activate 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 container CMD. In Dockerfile RUN 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 manipulate SHELL this way.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search