Docker

docker run -it: TTYs and Stdin

I was working on a project that involved running a docker command as a systemd service. The docker command looked something like the following.

docker run -it \
  foobar python3 -c 'import this; print(this)'

When I tried to start the systemd service I saw the following error.

Jan 27 18:37:37 host program[123]: the input device is not a TTY

After some research, I found other people who had the same problem. Their solution was to remove the -t option from the docker command. I tried it, and it worked. I submitted a pull request and my teammate asked what the difference between the two options was. I was a little embarrassed that I couldn’t explain it to him.

The documentation says

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

docker run –help

and,

-t, –tty Allocate a pseudo-TTY

docker run –help

I learn best by doing. So I created a simple Dockerfile to test out these different options.

FROM python:3-slim
CMD python

Then I built the image.

docker build -t dummy .

I ran the container in four different ways.

$ docker run dummy
$ docker run -i dummy
# .....waits for me to type something
print('hello')
# pressed Ctrl-D to signal End of Transmission
hello
$ docker run -t dummy
Python 3.8.1 (default, Jan  3 2020, 22:55:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> ^C^Fexit
^D
^C
# ... help! I cannot escape
# because a psuedo-terminal has not been allocated
$ docker run -it dummy
Python 3.8.1 (default, Jan  3 2020, 22:55:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
KeyboardInterrupt
>>>
# I pressed Ctrl-C to kill the process

Summary

-i by itself tells docker to connect the terminal stdin to the container’s stdin.

$ docker run -i alpine sh
ls /var
cache
empty
lib
local
lock
log
mail
opt
run
spool
tmp

Notice there is no shell prompt. There is no pseudo-terminal, the alpine container is just using the stdin of its parent process. This is not user-friendly for interactive shell sessions inside containers.

-t tells the docker process that input is coming not only from stdin, but specifically a terminal device (aka your shell session), so it allocates a pseudo-terminal for the container.

$ docker run -it alpine sh
/ # ls /var
cache  empty  lib    local  lock   log    mail   opt    run    spool  tmp
/ #

If you remove -t from the docker run command, you are telling docker that you don’t need a pseudo-terminal allocated for the container. You will still be able to type commands into it (for example, Ctrl-C will be read by the container if you want to kill it), but you won’t get a shell prompt.

This is a good post that helped me understand what’s going on.

Leave a Reply

Your email address will not be published. Required fields are marked *