Confused about Docker -t option to Allocate a pseudo-TTY
The -t
option goes to how Unix/Linux handles terminal access. In the past, a terminal was a hardline connection, later a modem based connection. These had physical device drivers (they were real pieces of equipment). Once generalized networks came into use, a pseudo-terminal driver was developed. This is because it creates a separation between understanding what terminal capabilities can be used without the need to write it into your program directly (read man pages on stty
, curses
).
So, with that as background, run a container with no options and by default you have a stdout stream (so docker run | <cmd>
works); run with -i
, and you get stdin stream added (so <cmd> | docker run -i
works); use -t
, usually in the combination -it
and you have a terminal driver added, which if you are interacting with the process is likely what you want. It basically makes the container start look like a terminal connection session.
Most of the answers here are great conceptual answers, but I found that they left out too many details for me to be able to use the information while sitting at the computer. Ahmed Gnomin's answer is on its way to being programmatic, but let's try to push it one step further.
First a bit of theory
Two images in The TTY Demystified are key:
I can't claim to fully understand this picture, but the relationship of interest here is that when xterm (or gnome-terminal in ubuntu; represented by one of the "user process" bubbles in the above image) opens up, it starts a bash (or whichever default shell), and then sends keyboard inputs to it via the kernel pseudo-terminal (PTY) master and slave:
xterm -> ptmx (pty master) -> pts (pty slave) -> bash
The second image represents the processes' involved in this short bash session:
>>> cat
>>> ls | sort
...
The key bits of information are the TTY and stdin, stdout, stderr lines. This shows that each process is associated to a TTY (teletype terminal), and that their 3 streams (stdin, stdout, stderr) are quite naturally associated to this TTY, except in the case of pipes or redirections (notice that the pipe ls | sort
associates ls' stdout to sort's stdin).
Now a bit of testing out the theory
We can find the pseudo-terminal used by bash by typing tty
:
>>> tty
/dev/pts/2
Bash is thus associated to the PTY slave number 2 (this probably means that there is another terminal open, associated to the master/slave pair 1). We can also get bash's stdin, stdout, and stderr streams:
>>> ls -l /proc/$$/fd
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 0 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 1 -> /dev/pts/2
lrwx------ 1 samlaf samlaf 64 Jun 17 21:50 2 -> /dev/pts/2
Indeed, they are all associated with bash's natural TTY slave. ($$
is a bash variable which returns bash's PID. We can equally find it by using ps
and typing it by hand).
And finally using this theory to answer the initial Docker question
We reproduce the above steps, but this time inside a docker container:
>>> docker run --rm -t ubuntu tty
/dev/pts/0
>>> docker run --rm ubuntu tty
not a tty
which makes sense since -t
allocates a pseudo-terminal.
The -i
related commands are harder to interpret.
>>> docker run --rm ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:37 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:37 1 -> pipe:[9173789]
l-wx------ 1 root root 64 Jun 18 02:37 2 -> pipe:[9173790]
>>> docker run --rm -t ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0
>>> docker run --rm -it ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Jun 18 02:39 2 -> /dev/pts/0
I still can't figure out what exactly -i
does... I would love some help!
The only interesting command I could find in which it seems to make a distinction is:
>>> docker run --rm -a stdout -i ubuntu bash -c "ls -l /proc/\$\$/fd"
lr-x------ 1 root root 64 Jun 18 02:43 0 -> pipe:[9199896]
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9199897]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9199898]
>>> docker run --rm -a stdout ubuntu bash -c "ls -l /proc/\$\$/fd"
lrwx------ 1 root root 64 Jun 18 02:43 0 -> /dev/null
l-wx------ 1 root root 64 Jun 18 02:43 1 -> pipe:[9197938]
l-wx------ 1 root root 64 Jun 18 02:43 2 -> pipe:[9197939]
The Docker documentation mentions that -a "attaches to the stream passed as input", but I haven't been able to find an explanation for what this means, and how its related to the -i
options.
Late answer, but might help someone
docker run/exec -i
will connect the STDIN of the command inside the container to the STDIN of the docker 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
.- On the other hand
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 the cat
container still running. This is because cat
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 of docker run
which is a terminal. docker run/exec
will make sure that its own input is in fact a tty before passing it to cat
. This is why you will get a input device is not a TTY
if you try echo "hello" | docker run -it alpine cat
because in this case, the input of docker run
itself is the pipe from the previous echo and not the terminal where docker run
is executed
Finally, why would you need to pass -t
if -i
will do the trick of connecting your input to cat
's input? This is because commands treat the input differently if it's a terminal. This is also best illustrated by example
docker run -e MYSQL_ROOT_PASSWORD=123 -i mariadb mysql -u root -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 as shell
were not treating the input as a tty and thus did not use tty specific behavior like masking the input or coloring the output.
The -t
argument is NOT documented well, or mentioned by many people often, according to a Google search.
It doesn't even show up when you display a list of (what should be) all docker client arguments by typing docker
at the Bash prompt (with the latest version of 1.8.1).
In fact, if you try to get specific help about this argument by typing docker -t --help
if gives this amazingly vague reply:
flag provided but not defined: -t
So, you can't be blamed for being confused about this argument!
There is a mention in the Docker online documentation which says it is to "Allocate a pseudo-tty" and is often used with -i
:
https://docs.docker.com/reference/run/
I saw it used in the documentation for the terrific jwilder/nginx-proxy
docker container in the following way:
docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx
In this case, what it does is send the output to the 'virtual' tty (Bash command prompt/terminal) within this docker container. You can then see this output by running the docker command docker logs CONTAINER
where CONTAINER
is the first couple of characters of this container's ID. This CONTAINER ID can be found by typing docker ps -a
I've seen this -t
argument mentioned briefly in the following link, where it says
The
-t
and-i
flags allocate a pseudo-tty and keep stdin open even if not attached. This will allow you to use the container like a traditional VM as long as the bash prompt is running.
https://coreos.com/os/docs/latest/getting-started-with-docker.html
I hope this helps! I'm not sure why this isn't documented or used much. Maybe it's experimental and will be implemented as a documented feature in upcoming versions.