What does it mean to attach a tty/std-in-out to dockers or lxc?
Solution 1:
stdin, stdout, and ttys
are related concepts. stdin
and stdout
are the input and output streams of a process. A pseudo terminal (also known as a tty
or a pts
) connects a user's "terminal" with the stdin
and stdout
stream, commonly (but not necessarily) through a shell such as bash
. I use quotes around "terminal" since we really don't use a terminal in the same sense today.
In the case of docker, you'll often use -t
and -i
together when you run processes in interactive mode, such as when starting a bash
shell. In the case of the shell you want to be able to issue commands and read the output.
The code docker uses to attach stdout/stdin
has all the dirty details.
Solution 2:
We can see what is happening under the hood by using the lsof
command. For a demonstration we can create a simple docker container from a Debian image that just runs sleep:
docker run -d --name tty-test debian /bin/bash -c "sleep 1000"
This will start the sleep command in a new container (note that we did not use -i
or -t
).
Next we "login" into our container though the exec
command and start a bash:
docker exec -it tty-test /bin/bash
A plain debian image will not have lsof
installed so we need to install it:
apt update && apt install -y lsof
Next we run lsof:
lsof
If run without any options, lsof
will print open files for all running processes. You should see three processes in its output (sleep, bash, and lsof itself).
Here are the relevant lines are those that show the file descriptors (FD column) 0
to 2
.
Note how the sleep
process, which we started without the -t option has three FIFO pipes for stdin
,stdout
and stderr
:
sleep 1 root 0r FIFO 0,10 0t0 8226490 pipe
sleep 1 root 1w FIFO 0,10 0t0 8226491 pipe
sleep 1 root 2w FIFO 0,10 0t0 8226492 pipe
While the bash
process has an actual device attached to stdin
, stdout
and stderr
:
bash 7 root 0u CHR 136,15 0t0 18 /dev/pts/15
bash 7 root 1u CHR 136,15 0t0 18 /dev/pts/15
bash 7 root 2u CHR 136,15 0t0 18 /dev/pts/15
Lets create another container with the -t
option:
docker run -d -t --name tty-test2 debian /bin/bash -c "sleep 1000"
After installing lsof
again (see above) we get a different output from lsof
for the sleep
process:
sleep 1 root 0u CHR 136,15 0t0 18 /15
sleep 1 root 1u CHR 136,15 0t0 18 /15
sleep 1 root 2u CHR 136,15 0t0 18 /15
Note how the type column has changed to CHR
and the name column shows /15
.
Finally, when we omit the -t
option from the exec
command and like this:
docker exec -it tty-test /bin/bash
then we can notice two things. First, we do not get a shell prompt from the bash now, but we can still type commands and see their output. When we run lsof
we see that the bash
process now also has pipes rather then a tty attached to stdin
, stdout
, and stderr
bash 379 root 0r FIFO 0,10 0t0 8263037 pipe
bash 379 root 1w FIFO 0,10 0t0 8263038 pipe
bash 379 root 2w FIFO 0,10 0t0 8263039 pipe