Understanding “the input device is not a TTY” Error in Docker: Terminal Allocation Explained


2 views

When running Docker containers with interactive terminal flags, you might encounter the cryptic error: the input device is not a TTY. This occurs specifically when combining pipe input with the -it flags:

$ echo 'hi there' | docker run -it ubuntu cat
the input device is not a TTY

The -t flag requests a pseudo-terminal (PTY) allocation from Docker, while -i keeps STDIN open. The conflict arises because:

  • Pipes provide non-interactive STDIN (just a data stream)
  • PTY expects an interactive terminal device
  • Docker enforces this distinction strictly

Working Scenario (Non-TTY):

# Using only -i for pipe input
$ echo 'data' | docker run -i alpine cat
data

Interactive Scenario (TTY Required):

# Proper TTY allocation for interactive shells
$ docker run -it alpine sh
/ # 

The error occurs at the kernel level when Docker's PTY allocation fails because:

  1. The pipe creates a non-terminal file descriptor
  2. libcontainer attempts to allocate a PTY
  3. The kernel rejects the allocation (ENOTTY)

For complex cases requiring both pipe input and TTY features:

# Using named pipes for advanced scenarios
$ mkfifo mypipe
$ echo "data" > mypipe & docker run -it --rm alpine sh -c 'cat < /mypipe'

Check terminal allocation status with:

$ docker run -it alpine sh -c 'ls -l /proc/self/fd/0'
lrwx------ 1 root root 64 Jan 1 00:00 /proc/self/fd/0 -> /dev/pts/0

When piping input to docker run -it, you encounter "the input device is not a TTY" because of conflicting terminal allocation requirements. Let's dissect exactly what happens at the OS level.

TTY (TeleTYpewriter) refers to terminal interfaces in Unix-like systems. The -t flag requests a pseudo-terminal (pty), which expects:

  • Interactive session capabilities (line editing, signal handling)
  • Terminal control characters (Ctrl+C, Ctrl+D processing)
  • Full duplex communication

When you pipe input (echo 'hi' | docker run...):

# This works because STDIN comes from pipe (non-TTY)
$ echo 'hi' | docker run -i ubuntu cat
hi

# This fails because -t requires TTY but gets pipe
$ echo 'hi' | docker run -it ubuntu cat
the input device is not a TTY

The Docker daemon performs this check:

  1. When -t is present, Docker calls isatty() on STDIN
  2. Pipes return false for isatty()
  3. Docker enforces TTY allocation only for actual terminals

Option 1: Remove -t when piping (best for automation)

# For non-interactive pipe scenarios
echo "data" | docker run -i alpine cat

Option 2: Force pseudo-TTY allocation (for edge cases)

# Using script to emulate TTY behavior
echo "data" | script -q /dev/null docker run -it alpine cat

For complex cases requiring both piped input and TTY features:

# Create temporary file for input
echo "input" > tmpfile && \
docker run -it --mount type=bind,src=$(pwd)/tmpfile,dst=/input.txt alpine sh -c "cat /input.txt && exec sh"

Check STDIN type with this diagnostic container:

docker run --rm -it alpine sh -c 'ls -l /proc/self/fd/0 && tty'

Example outputs:

  • Pipe input: lr-x------ 1 root root 64 May 10 00:00 /proc/self/fd/0 -> 'pipe:[12345]'
  • TTY input: lrwx------ 1 root root 64 May 10 00:00 /proc/self/fd/0 -> /dev/pts/0