Εxecute commands with args and override entrypoint on docker run - bash

I am trying to override the entrypoint in a docker image with a script execution that accepts arguments, and it fails as follows
▶ docker run --entrypoint "/bin/sh -c 'my-script.sh arg1 arg2'" my-image:latest
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/bin/sh -c 'myscript.sh arg1 arg2'": stat /bin/sh -c 'my-script.sh arg1 arg2': no such file or directory: unknown.
However when I exec to the container, the above command succeeds:
▶ docker run --entrypoint sh -it my-image:latest
~ $ /bin/sh -c 'my-script.sh arg1 arg2'
Success
Am I missing sth in the syntax?

Remember that arguments after the container image name are simply passed to the ENTRYPOINT script. So you can write:
docker run --entrypoint my-script.sh my-image:latest arg1 arg2
For example, if I have my-script.sh (mode 0755) containing:
#!/bin/sh
for arg in "$#"; do
echo "Arg: $arg"
done
And a Dockerfile like this:
FROM docker.io/alpine:latest
COPY my-script.sh /usr/local/bin/
ENTRYPOINT ["date"]
Then I can run:
docker run --rm --entrypoint my-script.sh my-image arg1 arg2
And get as output:
Arg: arg1
Arg: arg2
If you want to run an arbitrary sequence of shell commands, you can of course do this:
docker run --rm --entrypoint sh my-image \
-c 'ls -al && my-script.sh arg1 arg2'

If you need to do this at all regularly, you can refactor your Dockerfile to make this easier to do.
A Docker container's main process is run by concatenating together the "entrypoint" and "command" argument lists. In a Dockerfile, these come from the ENTRYPOINT and CMD directives. In the docker run command this is trickier: anything after the image name is the "command" part, but the "entrypoint" part needs to be provided by the --entrypoint argument, it needs to be before the image name, and it can only be a single word.
If you need to routinely replace the command, the syntax becomes much cleaner if you set it using CMD and not ENTRYPOINT in the Dockerfile.
# Dockerfile
CMD ["some", "main", "command"] # not ENTRYPOINT
If you make this change, then you can just put your alternate command after the image name in the docker run command, without a --entrypoint option and without splitting the command string around the image name.
docker run my-image:latest /bin/sh -c 'my-script.sh arg1 arg2'
I will somewhat routinely recommend a pattern where ENTRYPOINT is a wrapper script that does some first-time setup, then does something like exec "$#" to run the command that's passed to it as arguments. That setup is compatible with this CMD-first approach: the entrypoint wrapper will do its setup and then run the override command instead of the image's command.

Related

docker run entrypoint with multiple commands

How can I have an entrypoint in a docker run which executes multiple commands?
Something like:
docker run --entrypoint "echo 'hello' && echo 'world'" ... <image>
The image I'm trying to run, has already an entrypoint set in the Dockerfile, so solution like the following seems not to work, because it looks my commands are ignored, and only the original entrypoint is executed
docker run ... <image> bash -c "echo 'hello' && echo 'world'"
In my use-case I must use the docker run command. Solution which change the Dockerfile are not acceptable, since it is not in my hands
As a style point, this gets vastly easier if your image has a CMD that can be overridden. If you only need to run one command with no initial setup, make it be the CMD and not the ENTRYPOINT:
CMD ./some_command # not ENTRYPOINT
If you need to do some initial setup and then launch the main command, make the ENTRYPOINT be a shell script that ends with the special instruction exec "$#". The CMD will be passed into it as parameters, and this line replaces the shell script with that command.
#!/bin/sh
# entrypoint.sh
... do first time setup, run database migrations, set variables ...
exec "$#"
# Dockerfile
...
ENTRYPOINT ["./entrypoint.sh"] # MUST be JSON-array syntax
CMD ./some_command # as before
If you do these things, then you can use your initial docker run form. This will replace the CMD but leave the ENTRYPOINT intact. In the wrapper-script case, your alternate command will be run as the exec "$#" command, so all of the first-time setup will be done first.
# Assuming the image correctly honors the CMD
docker run ... \
image-name \
sh -c 'echo "foo is $FOO" && echo "bar is $BAR"'
If you really can't do this, you can override the docker run --entrypoint. This runs instead of the image's entrypoint (if you want the image's entrypoint you have to run it yourself), and the syntax is awkward:
# Run a shell command instead of the entrypoint
docker run ... \
--entrypoint /bin/sh \
image-name \
-c 'echo "foo is $FOO" && echo "bar is $BAR"'
Note that the --entrypoint option comes before the image name, and its arguments come after the image name.

Docker exec quoting variables

I'd like to know if there's a way to do this
Let's say the dockerfile contains this line, that specifies path of an executable
ENV CLI /usr/local/bin/myprogram
I'd like to be able to call this program using ENV variable name through exec command.
For example
docker exec -it <my container> 'echo something-${CLI}
Expecting
something-/usr/local/bin/myprogram
However that returns:
OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"${CLI} do something\": executable file not found in $PATH": unknown
Ok, I found a way to do it, all you need to do is evaluate command with bash
docker exec -it <container id> bash -c 'echo something-${CLI}'
returns something-/usr/local/bin/myprogram
If the CLI environment variable is not already set in the container, you can also pass it in such as:
docker exec -it -e CLI=/usr/local/bin/myprogram <container id> bash -c 'echo something-${CLI}'
See the help file:
docker exec --help
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
Run a command in a running container
Options:
-d, --detach Detached mode: run command in the background
-e, --env list Set environment variables
....
In it's original revision docker exec -it <my container> '${CLI} do something' with the expectation that ${CLI} will be substituted with /usr/local/bin/myprogram (as the exec COMMAND) and everything after passed as ARG's to /usr/local/bin/myprogram will not work, this is clearly documented: https://docs.docker.com/engine/reference/commandline/exec/
COMMAND should be an executable, a chained or a quoted command will not work. Example:
docker exec -ti my_container "echo a && echo b" will not work, but
docker exec -ti my_container sh -c "echo a && echo b" will.
Following the documentation, this will work as expected: docker exec -ti my_container sh -c "${CLI} foo", ${CLI} will be be executed after variable expansion and the argument(s) passed to the shell script set in ${CLI} (e.g. sh -c /usr/local/bin/myprogram foo).
Alternatively you could set the ENTRYPOINT to your script and pass in arguments with CMD or at the command line with docker run for example:
Given the below directory structure:
.
├── Dockerfile
└── example.sh
The Dockerfile contents:
FROM ubuntu:18.04
COPY example.sh /bin
RUN chmod u+x /bin/example.sh
ENTRYPOINT ["/bin/example.sh"]
CMD ["bla"]
And the example.sh script contents:
#!/bin/bash
echo $1
The CMD specified in the Dockerfile after the ENTRYPOINT will be the default argument for your script and you can override the default argument on the command line (assuming that the image is built and tagged as example:0.1):
user#host> docker run --rm example:0.1
bla
user#host> docker run --rm example:0.1 "arbitrary text"
arbitrary text
Note: this is my go to article for differences between ENTRYPOINT and CMD in Dockerfile's: https://medium.freecodecamp.org/docker-entrypoint-cmd-dockerfile-best-practices-abc591c30e21

Docker starting container process caused "exec: \"arg\": executable file not found in $PATH": unknown.

My task is to create Dockerfile such that it works the following way:
docker build -t test .
Returns Image named test successfully created
docker run --rm test
Returns Hello world!
docker run --rm test Universe
Returns Hello Universe!
What I have so far:
Dockerfile
FROM ubuntu:14.04
LABEL maintainer="trthhrtz"
CMD if [ $# -eq 0 ]; then echo "Hello world!"; else echo "Hello " + $# + "!"; fi
It does not work in case of input arguments, the error is:
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"Universe\": executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled
It would be easier to:
define an entrypoint script entrypoint.sh with your command logic scripted in it.
COPY that file in your Dockerfile
leave CMD undefined
That way, any additional parameter to your docker run -it --rm myImage arg1 arg2 ... command will be passed to the bash entrypoint.sh script, which will interpret $# correctly, as illustrated in "What does set -e and exec "$#" do for docker entrypoint scripts?".
See "Passing arguments from CMD in docker" for more.
Make sure you have a correct order of arguments when you do docker command.
For example:
docker run --name test-ubuntu -it d37f4165b5d2 bash
instead of
docker run --name test-ubuntu d37f4165b5d2 -it bash

Source script on interactive shell inside Docker container

I want to open a interactive shell which sources a script to use the bitbake environment on a repository that I bind mount:
docker run --rm -it \
--mount type=bind,source=$(MY_PATH),destination=/mnt/bb_repoistory \
my_image /bin/bash -c "cd /mnt/bb_repoistory/oe-core && source build/conf/set_bb_env.sh"
The problem is that the -it argument does not seem to have any effect, since the shell exits right after executing cd /mnt/bb_repoistory/oe-core && source build/conf/set_bb_env.sh
I also tried this:
docker run --rm -it \
--mount type=bind,source=$(MY_PATH),destination=/mnt/bb_repoistory \
my_image /bin/bash -c "cd /mnt/bb_repoistory/oe-core && source build/conf/set_bb_env.sh && bash"
Which spawns an interactive shell, but none of the macros defined in set_bb_env.sh
Would there be a way to provide a tty with the script properly sourcered ?
The -it flag is conflicting with the command to run in that you're telling docker to create the pseudo-terminal (ptty), and then running a command in that terminal (bash -c ...). When that command finishes, then the run is done.
What some people have done to work around this is to only have export variables in their sourced environment, and the last command would be exec bash. But if you need aliases or other items that aren't inherited like that, then your options are a bit more limited.
Instead of running the source in a parent shell, you could run it in the target shell. If you modified your .bash_profile to include the following line:
[ -n "$DOCKER_LOAD_EXTRA" -a -r "$DOCKER_LOAD_EXTRA" ] && source "$DOCKER_LOAD_EXTRA”
and then had your command be:
... /bin/bash -c "cd /mnt/bb_repository/oe-core && DOCKER_LOAD_EXTRA=build/conf/set_bb_env.sh exec bash"
that may work. This tells your .bash_profile to load this file when the env variable is already set, but not otherwise. (There can also be the -e flag on the docker command line, but I think that sets it globally for the entire container, which is probably not what you want.)

$# blank in Bash script which is ENTRYPOINT unless --entrypoint

Consider the following Dockerfile:
FROM ubuntu:xenial-20170802
RUN (echo '#!/bin/bash'; echo 'echo "args: $#"') > /run.sh && chmod a+x /run.sh
ENTRYPOINT /run.sh
Using Docker 1.12.6 (on an Ubuntu Zesty host, in case it matters), if I
docker build -t entrypoint-test .
docker run --rm entrypoint-test a b c
I just get
args:
In other words, $# is not working. However this seems to indicate that the shell script itself is OK:
docker run --rm --entrypoint /bin/bash entrypoint-test /run.sh a b c
producing
args: a b c
as expected. Even stranger,
docker run --rm --entrypoint /run.sh entrypoint-test a b c
works. Note that compared to the original command, I am doing nothing but overriding the ENTRYPOINT directive with an --entrypoint of the same value. So why would that make it work?
I cannot see anything in docs that would suggest argv would be blank in some cases, perhaps involving #! executable scripts.
Adding
cat /proc/$$/cmdline
to the shell script confirms that the arguments are not being passed along unless --entrypoint is used:
/bin/bash^#/run.sh^#a^#b^#c^#
vs.
/bin/bash^#/run.sh^#
https://serverfault.com/a/647790/144361 pointed me in the right direction. These docs explicitly note that the “shell form” does not accept arguments. The following syntax works:
ENTRYPOINT ["/run.sh"]

Resources