254

I am new to the docker world. I have to invoke a shell script that takes command line arguments through a docker container. Ex: My shell script looks like:

#!bin/bash
echo $1

Dockerfile looks like this:

FROM ubuntu:14.04
COPY ./file.sh /
CMD /bin/bash file.sh

I am not sure how to pass the arguments while running the container

Akash Mehta
  • 2,779
  • 2
  • 15
  • 10

8 Answers8

299

with this script in file.sh

#!/bin/bash
echo Your container args are: "$@"

and this Dockerfile

FROM ubuntu:14.04
COPY ./file.sh /
ENTRYPOINT ["/file.sh"]

you should be able to:

% docker build -t test .
% docker run test hello world
Your container args are: hello world
Cristian
  • 198,401
  • 62
  • 356
  • 264
Partly Cloudy
  • 6,508
  • 3
  • 27
  • 16
  • 18
    If you forget the "" around "/file.sh" as I did, it won't work. – kev Mar 02 '17 at 23:43
  • 10
    for some reason, this doesnt work with `ENTRYPOINT ./file.sh` – phil294 Mar 17 '18 at 07:40
  • 13
    Don't forget to `chmod +x file.sh` to set the executable flag. – topskip Sep 04 '18 at 07:27
  • 2
    @kev , do you know why is it like that? what's the difference between `["/file.sh"]` and `/file.sh` or even `[/file.sh]` – Nitzan Oct 03 '18 at 12:11
  • 3
    @Nitzankin see my answer for why proper json formatting is needed. – BMitch Dec 22 '18 at 16:57
  • 1
    The short answer is one is a list of cmd + params to exec, while the other is a shell command to run. And only the exec form allows passing extra arguments from `docker run`. Details: https://docs.docker.com/engine/reference/builder/#entrypoint – Juan A. Navarro Jun 16 '20 at 12:50
110

Use the same file.sh

#!/bin/bash
echo $1

Build the image using the existing Dockerfile:

docker build -t test .

Run the image with arguments abc or xyz or something else.

docker run -ti --rm test /file.sh abc

docker run -ti --rm test /file.sh xyz
BMW
  • 42,880
  • 12
  • 99
  • 116
  • 55
    I think ENTRYPOINT is the way to go if you don't want the end-user to know about file.sh directly. – Grae Kindel Mar 15 '16 at 20:50
  • How can you just start a script like this `docker run -ti test /file.sh abc`. I feel that the script wont run because it should be `docker run -ti test sh /file.sh abc`. sh or /bin/sh will run it right. – Vamsidhar Muggulla Nov 04 '16 at 08:05
  • 1
    For anyone else coming here. The /usr/bin/env trick is optional style preference not a requirement of getting this to work. Also the #! line indicates which interpreter to use buy default. So it can run just by calling the script. – wheredidthatnamecomefrom Jan 05 '18 at 03:15
80

There are a few things interacting here:

  1. docker run your_image arg1 arg2 will replace the value of CMD with arg1 arg2. That's a full replacement of the CMD, not appending more values to it. This is why you often see docker run some_image /bin/bash to run a bash shell in the container.

  2. When you have both an ENTRYPOINT and a CMD value defined, docker starts the container by concatenating the two and running that concatenated command. So if you define your entrypoint to be file.sh, you can now run the container with additional args that will be passed as args to file.sh.

  3. Entrypoints and Commands in docker have two syntaxes, a string syntax that will launch a shell, and a json syntax that will perform an exec. The shell is useful to handle things like IO redirection, chaining multiple commands together (with things like &&), variable substitution, etc. However, that shell gets in the way with signal handling (if you've ever seen a 10 second delay to stop a container, this is often the cause) and with concatenating an entrypoint and command together. If you define your entrypoint as a string, it would run /bin/sh -c "file.sh", which alone is fine. But if you have a command defined as a string too, you'll see something like /bin/sh -c "file.sh" /bin/sh -c "arg1 arg2" as the command being launched inside your container, not so good. See the table here for more on how these two options interact

  4. The shell -c option only takes a single argument. Everything after that would get passed as $1, $2, etc, to that single argument, but not into an embedded shell script unless you explicitly passed the args. I.e. /bin/sh -c "file.sh $1 $2" "arg1" "arg2" would work, but /bin/sh -c "file.sh" "arg1" "arg2" would not since file.sh would be called with no args.

Putting that all together, the common design is:

FROM ubuntu:14.04
COPY ./file.sh /
RUN chmod 755 /file.sh
# Note the json syntax on this next line is strict, double quotes, and any syntax
# error will result in a shell being used to run the line.
ENTRYPOINT [ "/file.sh" ]

And you then run that with:

docker run your_image arg1 arg2

There's a fair bit more detail on this at:

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • 2
    I had experimented with setting my entrypoint to `["bash", "--login", "-c"]` to get the /etc/profile source in the image, but was later left wondering why no args would be passed to a shell script passed to docker run... Your answer cleared that up, thank you! – Apteryx Apr 09 '19 at 13:26
  • 1
    Shouldn't that be `ENTRYPOINT ["/file.sh"]`, i.e., with the preceding `/`? – Dave Jul 06 '23 at 10:26
70

With Docker, the proper way to pass this sort of information is through environment variables.

So with the same Dockerfile, change the script to

#!/bin/bash
echo $FOO

After building, use the following docker command:

docker run -e FOO="hello world!" test
Michael
  • 8,229
  • 4
  • 17
  • 14
  • 46
    Why is this the highest voted answer? Env vars are another way to pass info, but isn't what OP is asking. And of course there is absolutely nothing improper about OP's desire to pass args to the container. – Partly Cloudy Oct 28 '16 at 19:59
  • 8
    There's a ton of XY problems ask on SO. Since the OP stated they were new to Docker, it's perfectly reasonable an answer show the recommended way to achieve a goal. Thus making this a great answer. – colm.anseo May 26 '19 at 01:26
  • 2
    Simplest way to get the job done instead of passing the variables as build args and all that mess. Very useful for passing secrets as environment variables. – Arvind Sridharan Jun 22 '19 at 09:36
  • 1
    I had a tricky situation where I needed a parameter with spaces in it to be passed through to a command inside the entrypoint. `pytest -m "not longest"` This was the only solution that worked for me. "Proper" or not, I'm glad it was here. – mildewey Oct 17 '19 at 20:53
29

What I have is a script file that actually runs things. This scrip file might be relatively complicated. Let's call it "run_container". This script takes arguments from the command line:

run_container p1 p2 p3

A simple run_container might be:

#!/bin/bash
echo "argc = ${#*}"
echo "argv = ${*}"

What I want to do is, after "dockering" this I would like to be able to startup this container with the parameters on the docker command line like this:

docker run image_name p1 p2 p3

and have the run_container script be run with p1 p2 p3 as the parameters.

This is my solution:

Dockerfile:

FROM docker.io/ubuntu
ADD run_container /
ENTRYPOINT ["/bin/bash", "-c", "/run_container \"$@\"", "--"]
flaviut
  • 2,007
  • 3
  • 23
  • 32
jkh
  • 299
  • 3
  • 2
  • 8
    Replacing the third value in the `ENTRYPOINT` array with `"/run_container \"$@\""` means arguments containing spaces are handled correctly (e.g. `docker run image_name foo 'bar baz' quux`). – davidchambers Feb 06 '17 at 11:25
  • After adding switch/case statements to my bash file, ENTRYPOINT["run_container.sh"] no longer worked for me, but ENTRYPOINT["sh", "-c", "run_container.sh"] would no longer accept my parameters. This solution (with @davidchambers suggestion) worked for me. – rhamilton Apr 01 '18 at 05:39
11

If you want to run it @build time :

CMD /bin/bash /file.sh arg1

if you want to run it @run time :

ENTRYPOINT ["/bin/bash"]
CMD ["/file.sh", "arg1"]

Then in the host shell

docker build -t test .
docker run -i -t test
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • 3
    `ENTRYPOINT` is a good answer for the OP who I think wanted runtime, but if you really want build time variables this answer just is broken. Use `ARG` and `docker build --build-arg` https://docs.docker.com/engine/reference/builder/#arg – Grae Kindel Mar 15 '16 at 20:53
3

I wanted to use the string version of ENTRYPOINT so I could use the interactive shell.

FROM docker.io/ubuntu
...
ENTRYPOINT python -m server "$@"

And then the command to run (note the --):

docker run -it server -- --my_server_flag

The way this works is that the string version of ENTRYPOINT runs a shell with the command specified as the value of the -c flag. Arguments passed to the shell after -- are provided as arguments to the command where "$@" is located. See the table here: https://tldp.org/LDP/abs/html/options.html

(Credit to @jkh and @BMitch answers for helping me understand what's happening.)

user149100
  • 1,089
  • 3
  • 12
  • 16
-1

Another option...

To make this works

docker run -d --rm $IMG_NAME "bash:command1&&command2&&command3"

in dockerfile

ENTRYPOINT ["/entrypoint.sh"]

in entrypoint.sh

#!/bin/sh

entrypoint_params=$1
printf "==>[entrypoint.sh] %s\n" "entry_point_param is $entrypoint_params"

PARAM1=$(echo $entrypoint_params | cut -d':' -f1) # output is 1 must be 'bash' it     will be tested    
PARAM2=$(echo $entrypoint_params | cut -d':' -f2) # the real command separated by     &&

printf "==>[entrypoint.sh] %s\n" "PARAM1=$PARAM1"
printf "==>[entrypoint.sh] %s\n" "PARAM2=$PARAM2"

if [ "$PARAM1" = "bash" ];
then
    printf "==>[entrypoint.sh] %s\n" "about to running $PARAM2 command"
    echo $PARAM2 | tr '&&' '\n' | while read cmd; do
        $cmd
    done    
fi
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
wagnermarques
  • 140
  • 1
  • 4
  • some remarks and limitations.... command with ":" needs changes in cut -d':' and commands like docker run -d --rm $IMG_NAME "bash:echo $PATH" will show the host path value instead the host one – wagnermarques Jul 01 '19 at 15:41