9

I'm learning about Docker at the moment, and going through the Dockerfile reference, specifically the RUN instruction. There are two forms of RUN - the shell form, which runs the command in a shell, and the exec form, which "does not invoke a command shell" (quoted from the Note section).

If I understood the documentation correctly, my question is - If, and how can, Docker run a command without a shell?

Note that the answers to Can a command be executed without a shell?'s don't actually answer the question.

Community
  • 1
  • 1
dayuloli
  • 16,205
  • 16
  • 71
  • 126
  • 1
    How do you think the shell itself invokes an external command once it's parsed the command line you fed it? There *has* to be an OS-level interface it can call. (Also, the pass-a-string-to-a-shell approach has major security flaws -- shell injection vulnerabilities are easily avoided if you pass arguments out-of-band, either in the environment or on other argv positions, but `system(string_to_parse)` doesn't provide a convenient way to do either). – Charles Duffy Feb 15 '17 at 18:59

3 Answers3

11

If I understand your question correctly, you're asking how something can be run (specifically in the context of docker) without invoking a command shell.

The way things are run in the linux kernel are usually using the exec family of system calls.

You pass it the path to the executable you want to run and the arguments that need to be passed to it via an execl call for example.


This is actually what your shell (sh, bash, ksh, zsh) does under the hood anyway. You can observe this yourself if you run something like strace -f bash -c "cat /tmp/foo"

In the output of that command you'll see something like this:

execve("/bin/cat", ["cat", "/tmp/foo"], [/* 66 vars */]) = 0

What's really going on is that bash looks up cat in $PATH, it then finds that cat is actually an executable binary available at /bin/cat. It then simply invokes it via execve. and the correct arguments as you can see above.

You can trivially write a C program that does the same thing as well. This is what such a program would look like:

#include<unistd.h>

int main() {

    execl("/bin/cat", "/bin/cat", "/tmp/foo", (char *)NULL);

    return 0;
}

Every language provides its own way of interfacing with these system calls. C does, Python does and Go, which is what's used to write Docker for the most part, does as well. A RUN instruction in the docker likely translates to one of these exec calls when you hit docker build. You can run an strace -f docker build and then grep for exec calls in the log to see how the magic happens.


The only difference between running something via a shell and directly is that you lose out on all the fancy stuff your shell will do for you, such as variable expansion, executable searching etc.

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
ffledgling
  • 11,502
  • 8
  • 47
  • 69
  • I was unsure about this (that's why I search for [this question](http://unix.stackexchange.com/questions/203634/can-a-command-be-executed-without-a-shell)). You're telling me that it is possible to run a command without a shell? – dayuloli Feb 15 '17 at 18:46
  • @dayuloli, yes indeed it is. I'll update with an example in a bit. – ffledgling Feb 15 '17 at 18:48
  • After reading your answer I understand a lot more now, thank you! – dayuloli Feb 15 '17 at 20:01
  • Stumbling back at this question a few years later, there is one glaring mistake here: `strace -f docker build` will not actually showing the exec syscall, because the `docker` cli client does not actually run the build itself, the docker daemon does. Therefore one would have to attach an `strace` to the docker daemon's PID before issuing the `docker build` command to actually see the exec happening. – ffledgling Dec 03 '19 at 00:31
1

A program can execute another program without a shell; you just create a new process and load an executable onto it, so you don't need the shell for that. The shell is needed for a user to start a program because it is the user interface to the system. Also, a program is not able to run a built-in command like cd or rm without a shell because there's no executable to be found (there are alternative ways, thought, but not as simple).

Fabio Ceconello
  • 15,819
  • 5
  • 38
  • 51
  • And you'd need the shell to run a script because the interpreter is contained in it. – Fabio Ceconello Feb 15 '17 at 18:52
  • Thank you so much for this answer, it clarified so much for me. So here, when I am building my Docker image, if I use the shell form of `RUN`, it will spawn a new shell, let the shell interpret the command, and then run the interpreted command. If I use the *exec* form, the current process that is running `docker run` will create a new process, passing the executable to it. – dayuloli Feb 15 '17 at 18:52
  • Exactly. Thus running something without a shell is more lightweight, but with a few less capabilities. If the command you want to run is an executable (binary, not a script), and you don't need to translate something in the command line arguments (like environment variables) then the shell would be just a burden. – Fabio Ceconello Feb 15 '17 at 18:55
0

In very general - docker run will start container with its default process, when docker exec allow you to run any process you want inside the container.

For example, running microsoft/iis container by docker run microsoft/iiswill run default process, which is powershell.

But you can run cmd by running docker exec -i my_container cmd

See this answer for more details.

Community
  • 1
  • 1
evgenyl
  • 7,837
  • 2
  • 27
  • 32