Passing a command with arguments as a string to docker run

davidA picture davidA · Sep 12, 2016 · Viewed 12.8k times · Source

The issue I'm facing is how to pass a command with arguments to docker run. The problem is that docker run does not take command plus arguments as a single string. They need to be provided as individual first-class arguments to docker run, such as:

#!/bin/bash
docker run --rm -it myImage bash -c "(cd build && make)"

However consider the command and argument as the value of a variable:

#!/bin/bash -x
DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage "$DOCKER_COMMAND"

Unfortunately this doesn't work because docker run doesn't understand the substitution:

+ docker run --rm -it myImage 'bash -c "(cd build && make)"'
docker: Error response from daemon: oci runtime error: exec: "bash -c \"(cd build && make)\"": stat bash -c "(cd build && make)": no such file or directory.

A slight change, removing the quotation of DOCKER_COMMAND:

#!/bin/bash -x
DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage $DOCKER_COMMAND

Results in:

+ docker run --rm -it myImage 'bash -c "(cd build && make)"'
build: -c: line 0: unexpected EOF while looking for matching `"'
build: -c: line 1: syntax error: unexpected end of file

How can I expand a string from a variable so that it is passed as a distinct command and arguments to docker run inside a script?

Answer

larsks picture larsks · Sep 13, 2016

Start with the syntax of the docker run command, which is:

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

This means if you run:

DOCKER_COMMAND='bash -c "(cd build && make)"'
docker run --rm -it myImage "$DOCKER_COMMAND"

You are passing the entirety of the $DOCKER_COMMAND variable as the COMMAND. You are asking Docker to find a file matching the name bash -c "(cd build && make)", so it should be no surprise that it fails. It doesn't have anything to do with "docker run doesn't understand the substitution". This is all related to the way your shell parses command lines before executing them.

When you remove the quotes around $DOCKER_COMMAND, you end up calling it like this (I'm putting each argument on a separate line to make it obvious):

docker
run
--rm
-it
myImage
bash
-c
"(cd
build
&&
make)"

And that's not going to work, because bash is going to try to run the script "(cd, which should make obvious the reason for the unexpected EOF while looking for matching"'error. Bash's-c` option only takes a single argument, but because of the way shell expansion works it's getting 4.

You could do it this way:

DOCKER_COMMAND='cd build && make'
docker run --rm -it myImage bash -c "$DOCKER_COMMAND"

(I've removed the parentheses around your command because they don't do anything the way you're using them.)

This way, you're calling docker run with a command of bash, and you're giving bash's -c option a single argument (the contents of the $DOCKER_COMMAND variable).