Understanding docker run --attach option

My point was that the command is not running on your client. The container is running on the server so the command is running on the server too in a container. :slight_smile: Of course if you are not using Docker Desktop or any other remote docker daemon, your client is the server. The container is just isolation and resouce limits. The process “thinks” it is running on a separate machine but it is still running on the host isolated from the rest of the environment.

When I saw @meyay’s post I wasn’t sure how that could work so I tried the command and couldn’t prove what I tried so I assumed I just didn’t understand. Reading your example gave me another way to test it.

Since we use Linux and macOS which also supports the syntax of $(</dev/stdin), it is easy to miss that $(anycommand) is actually running on your host, not in a container. You were able to write “hello there” into the log file because

echo "hello there!" | anycommand

means you write something to the standard output which becomes the standard input of anycommand but that command is interpreted on the host. In this case the command was docker and the following command was interpreted on the host:

docker run --rm -d -v $PWD/logs.txt:/tmp/logs.txt alpine /bin/sh -c "echo container: $(</dev/stdin) > /tmp/logs.txt"

The subshell syntax ($(anycommand)) is interpreted between quotion marks. By the time that command is interpreted by the shell, you have something in the standard input so $(</dev/stdin) can read it. Eventually the following command will be executed:

docker run --rm -d -v $PWD/logs.txt:/tmp/logs.txt alpine /bin/sh -c "echo container: hello there! > /tmp/logs.txt"

If you want to make sure the command is executed in the container, you need to use apostrophes:

echo "hello there!" | docker run --rm -d -v $PWD/logs.txt:/tmp/logs.txt alpine /bin/sh -c 'echo container: $(</dev/stdin) > /tmp/logs.txt'

Now it will write only "container: " to the log file.
This command however would be able to write the log file again properly

echo "hello there!" | docker run --rm -i -v $PWD/logs.txt:/tmp/logs.txt alpine /bin/sh -c 'echo container: $(</dev/stdin) > /tmp/logs.txt'

If I change Metin’s example like this:

echo "test" | docker run --rm alpine echo 'container:  $(</dev/stdin)' | echo "outside $(</dev/stdin)"

you will get

outside container:  $(</dev/stdin)

This is because it just echos a string. There is no shell to interpret it since it is between apostrophes. Your example used /bin/sh -c and everything else was its argument. This is similar to using the eval keyword. So the next command will show that the container cannot read the stdin.

echo "test" | docker run --rm alpine /bin/sh -c 'echo "container:  $(</dev/stdin)"' | echo "outside $(</dev/stdin)"

Here the argument of sh is between apostrophes and it will execute the string it gets.

outside container:

Let’s use -i

echo "test" | docker run --rm -i alpine /bin/sh -c 'echo "container:  $(</dev/stdin)"' | echo "outside $(</dev/stdin)"

It will still not work.
So our next problem is that this </dev/stdin is supported by bash and the alpine linux doesn’t have it by default. Replace alpine with “ubuntu” and replace /dev/sh with /bin/bash and it will work

echo "test" | docker run -i --rm ubuntu /bin/bash -c 'echo "container:  $(</dev/stdin)"' | echo "outside $(</dev/stdin)"
outside container:  test

Or you can use alpine (or ubuntu with /bin/sh) and replace $(</dev/stdin) with $(cat -)

echo "test" | docker run -i --rm alpine /bin/sh -c 'echo "container:  $(cat -)"' | echo "outside $(</dev/stdin)"
outside container:  test

I think we don’t use standard inputs and docker containers often so it is still not obvious to us. This is why I actually enjoyed this playing with the standard streams :slight_smile:

1 Like