395

What exactly does this option do? I've been reading a lot on TTY and I'm still confused. I played around without having the -t and just -i and it seems like programs that expect user input throw an error without the -t. Why is it important for pseudo-TTY to be enabled?

Channa
  • 742
  • 17
  • 28
user1099123
  • 6,063
  • 5
  • 30
  • 35

9 Answers9

465

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.

J. Scott Elblein
  • 4,013
  • 15
  • 58
  • 94
Twweeed
  • 4,690
  • 1
  • 11
  • 2
  • 16
    This should be the top answer. While it's not the most technical one here, it explains the fundamental behaviour of the `-it` flags. – Kris Khaira Aug 26 '18 at 20:03
  • 2
    Agree with Kris. I read the other answers and was still totally confused. This answer clears it up. – Ben Lee Sep 19 '18 at 19:55
  • 27
    Yes, its maybe worth of mentioning that "TTY" itself is an acronym coming from "teletypewriter" (AKA "teleprinter") word which was a name of device allowing you to type text and send it away in the same time - like a telephone for text ;-) Try `docker run -i ubuntu` and `docker run -it ubuntu` you'll see the difference immediately. "-i" allows you to make the container to wait for interaction from host but actual interaction from the console (terminal) is possible after you "allocate tty driver" with flag "-t". – Zegar Nov 22 '18 at 15:26
  • Can I start tty within the docker? I have some app that stops working the I do not run the docker with `-t`, but I can not modify the docker start command in production. So I need to make the app think it was started with `-t`. – mvorisek Jun 20 '19 at 01:30
307

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 process cat is waiting for input from the infinite stream that is the terminal input of the docker run.
  • On the other hand echo "hello" | docker run -i alpine cat will print "hello" and exit immediately because cat 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 while cat is connected to a terminal input, this input is not connected to your input. The "hello" that you typed did not reach the input of cat. 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 like ls 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.

lmcarreiro
  • 5,312
  • 7
  • 36
  • 63
Ahmed Ghonim
  • 4,455
  • 1
  • 17
  • 23
  • @Ahmed Ghonim, Very good answers. Thank you. But about "This is because commands treat the input differently if it's a terminal", I think that is a mistyping, right? It should be "This is because commands treat the input differently if it's *not* a terminal", right ? – tuq Jan 04 '20 at 09:49
  • 3
    @Ahmed Ghonim. Crystal clear. But what about docker run -a=stdin alpine cat? – HKIT Jan 29 '20 at 15:02
  • 1
    @HKIIT "-a=stdin" attaches stdin stream to the container but without a memory allocation. It's the -i flag that allocates buffer memory in the container for stdin stream, hence the description "Keep STDIN open even if not attached", when -i is passed memory is allocated for stdin regardless of attachment flags. Without this allocated memory reads to stdin are empty/eof. Also you need to include "-a=stdout" to see the response from cat command for example: "docker run -i -a=stdin -a=stdout alpine cat"... of course there's no need to do this you can just run "docker run -i alpine cat". – David D Mar 11 '20 at 02:14
  • 4
    "I have not found any useful use for the -t alone without being combined with -i" well you can for instance run `docker run -t -d image sh` and you would get a docker container running but doing nothing. You can this way use any command that expects input instead of using `yes > /dev/null` command inside the docker container to have it running without doing anything. I don't see the usefulness of this, though. – Adrian Apr 25 '20 at 15:44
78

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

grg
  • 5,023
  • 3
  • 34
  • 50
Rich
  • 933
  • 6
  • 6
  • 24
    The documentation shows up for `docker run --help`, not `docker -t --help`: `-t, --tty=false Allocate a pseudo-TTY` " – bskaggs Jan 03 '16 at 01:54
18

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:

enter image description here

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

enter image description here

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.

samlaf
  • 425
  • 4
  • 9
  • While this is interesting, it doesn't really answer the question "which makes sense since -t allocates a pseudo-terminal." is just what the manual says ;) "-i" allows you to interact with the container, i.e. type things that the container picks up. You need it if you are running interactive apps like bash. – mhvelplund Jun 19 '21 at 05:43
  • Can you "show" me what "-i" does? aka can you run a container with and without "-i" and show me that some property is changed somewhere. How is "allows you to interact with the container" reflected in the actual running process? – samlaf Jun 19 '21 at 15:59
  • If you read the other answers it should become clear, but you can also see it quickly by comparing the result of `docker run -ti alpine sh` and `docker run -t alpine sh`. The latter just exits because you need stdin (-i) to use a shell. – mhvelplund Jun 20 '21 at 17:22
  • That makes sense. On my computer it doesn't exit though, it just kind of sits there in limbo; I can continue typing to the terminal but nothing gets sent it seems. But I still don't understand why, since `docker run --rm -t ubuntu bash -c "ls -l /proc/\$\$/fd"` shows that stdin is connected to /dev/pts/0. – samlaf Jun 21 '21 at 14:55
14

The -it combined options are known as interactive mode.

By default, containers only have an stdout stream (i.e., docker run | CMD works), to interact with our container, we need these two options:

  • -i adds an stdin stream (i.e., CMD | docker run works);
  • -t allocates a pseudo-TTY master/slave pair with the slave part tied to the running process in the container and the master part tied to your docker command.

The stdin stream attaches the container to the stdin of your shell (docker inherits the stdin stream of your shell) while the TTY line discipline gives you the ability to interact with the container in a keyboard fashion.

The TTY line discipline consists of low-level features provided by the kernel to TTY devices, such as an editing buffer and basic line edition commands.

As shown below, you can check the standard file descriptors with the following command:

docker run --rm -i ubuntu sh -c "ls -l /proc/\$\$/fd"

If you remove the -i you'll see that stdin points to /dev/null (i.e., no stream is allocated).

explogx
  • 1,159
  • 13
  • 28
5

What I know about the -t is the following:

docker exec -ti CONTAINER bash - allows me to "login" in the container. It feels like ssh-ing (it's not).

But the trouble was when I wanted to restore a database.

Usually I dodocker exec -ti mysql.5.7 mysql - Here I execute the mysql command in the container and get an interactive terminal.

I added <dump.sql to the previous command so I can restore a db. But it failed with cannot enable tty mode on non tty input.

Removing the -t helped. Still don't understand why:

docker exec -i mysql.5.7 mysql < dump.sql

The last one works. Hope this helps people.

mist
  • 1,853
  • 2
  • 19
  • 33
  • Can I start tty within the docker? I have some app that stops working the I do not run the docker with `-t`, but I can not modify the docker start command in production. So I need to make the app think it was started with `-t`. – mvorisek Jun 20 '19 at 01:30
3

Every process has three data streams i.e STDIN/ STDOUT/ STDERR. When a process is running in a container, by default terminal is connected with STDOUT stream of the process running in the container. Hence all the output streams will be visible while running docker run command in the terminal. But if you want to provide input to the running process in the container then you have to connect with STDIN channel of the process which is not by default and is done with docker run -i command.

-t is used for interactive/ formatted input operations.

Harsh Vardhan
  • 111
  • 1
  • 4
-1

In linux when you run a command, you need a terminal (tty) to execute it.

So when you want to connect to docker (or run command in docker container), you have to provide the option -t which takes in the consideration of terminal inside the docker container.

Chirag
  • 408
  • 1
  • 4
  • 8
  • 1
    I'm not sure why you think you need a tty for this. Many programms invoke other executables without a tty. In this case STDIN/STDOUT are just normal input/output streams. – Sebi2020 Oct 09 '20 at 13:35
-6

The -it instructs Docker to allocate a pseudo-TTY connected to the container’s stdin, creating an interactive bash shell in the container.

--interactive, -i false Keep STDIN open even if not attached

--tty, -t false Allocate a pseudo-TTY

https://docs.docker.com/engine/reference/commandline/run/

J. Scott Elblein
  • 4,013
  • 15
  • 58
  • 94
Droid Teahouse
  • 893
  • 8
  • 15