This is a command that works:
$ echo 'hi there' | docker run -i ubuntu cat
hi there
This is a command that responds with an error message:
$ echo 'hi there' | docker run -it ubuntu cat
the input device is not a TTY
I would like to figure out exactly what happens here. Not just "remove -t and it'll be fixed".
I know that docker run
's -t
option stands for "Allocate a pseudo-TTY", and I have read historical overviews of what TTY stands for, but it didn't help me understand what kind of a contract is violated here.
Best Answer
Late answer, but might help someone
docker run/exec -i
will connect the STDIN of the command inside the container to the STDIN of thedocker run/exec
itself.So
docker run -i alpine cat
gives you an empty line waiting for input. Type "hello" you get an echo "hello". The container will not exit until you send CTRL+D because the main processcat
is waiting for input from the infinite stream that is the terminal input of thedocker run
.echo "hello" | docker run -i alpine cat
will print "hello" and exit immediately becausecat
notices that the input stream has ended and terminates itself.If you try
docker ps
after you exit either of the above, you will not find any running containers. In both cases,cat
itself has terminated, thus docker has terminated the container.Now for "-t", this tells the main process inside docker that its input is a terminal device.
So
docker run -t alpine cat
will give you an empty line, but if you try to type "hello", you will not get any echo. This is because whilecat
is connected to a terminal input, this input is not connected to your input. The "hello" that you typed did not reach the input ofcat
.cat
is waiting for input that never arrives.echo "hello" | docker run -t alpine cat
will also give you an empty line and will not exit the container on CTRL-D but you will not get an echo "hello" because you didn't pass-i
If you send CTRL+C, you get your shell back, but if you try
docker ps
now, you see thecat
container still running. This is becausecat
is still waiting on an input stream that was never closed. I have not found any useful use for the-t
alone without being combined with-i
.Now, for
-it
together. This tells cat that its input is a terminal and in the same time connect this terminal to the input ofdocker run
which is a terminal.docker run/exec
will make sure that its own input is in fact a tty before passing it tocat
. This is why you will get ainput device is not a TTY
if you tryecho "hello" | docker run -it alpine cat
because in this case, the input ofdocker run
itself is the pipe from the previous echo and not the terminal wheredocker run
is executedFinally, why would you need to pass
-t
if-i
will do the trick of connecting your input tocat
's input? This is because commands treat the input differently if it's a terminal. This is also best illustrated by exampledocker run -e MYSQL_ROOT_PASSWORD=123 -i mariadb mysql -uroot -p
will give you a password prompt. If you type the password, the characters are printed visibly.docker run -i alpine sh
will give you an empty line. If you type a command likels
you get an output, but you will not get a prompt or colored output.In the last two cases, you get this behavior because
mysql
as well asshell
were not treating the input as a tty and thus did not use tty specific behavior like masking the input or coloring the output.