Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Fix Input Device Is Not a TTY with Docker Compose in CI

fix-input-device-is-not-a-tty-with-docker-compose-in-ci.jpg

We'll get things to dynamically work with stdin and stdout and allow reusing Docker Compose commands when a TTY is available.

Quick Jump:

Let’s go over a practical example, how this error could pop up and how to fix it. For reference the error you might be getting is the input device is not a TTY. This could happen when dealing with stdin and stdout.

In my Docker Compose starter app projects I use a run script which has a number of shortcut commands.

For example you can do ./run psql to connect to the PostgreSQL container with a psql prompt. This is a shortcut so you don’t have to run docker compose exec postgres psql. Here we want to open up an interactive prompt since it’s expected you’ll be running commands. Likewise you can do gunzip -c db-dump.sql.gz | ./run psql to import a DB dump.

If you tried to run docker compose exec psql -c 'SELECT 1' in CI (a common environment where is a TTY isn’t available) then you’d likely get that error because Docker Compose can’t allocate a TTY which it does by default. You’d get the same problem if you ran gunzip -c db-dump.sql.gz | docker compose exec postgres psql.

To get around that, you can run docker compose exec -T ... to instruct Docker Compose to avoid allocating a TTY. You can also use --no-tty for the long form flag instead.

This is where the run script shortcut is handy because we can check to see if a TTY is available for stdin or stdout and then dynamically set that flag if needed. This gives you the best of both worlds for being able to use your shortcut locally on your dev box and in CI.

This is a pattern I’ve been using for years. Here’s the relevant snippets from the run script:

# Disable tty allocation for Docker Compose when none is available, such as
# in CI or when you pipe something into the run script. 0 = stdin, 1 = stdout.
TTY="${TTY:-}"
if ! [ -t 0 ] || ! [ -t 1 ]; then
  TTY="-T"
fi

_dc() {
  docker compose "${DC}" ${TTY} "${@}"
}

psql() {
  . .env
  _dc postgres psql -U "${POSTGRES_USER}" "${@}"
}

Notice the _dc function references $TTY which either has -T or is empty. Then other functions like psql use it. I use this pattern because many other functions in the run script reference _dc.

The real takeaway is ! [ -t 0 ] || ! [ -t 1 ] which checks to see if a TTY is available for stdin OR stdout. We want to check for both because the -t 0 check handles piping text into ./psql and -t 1 handles outputting text.

In the video below we’ll break this down even further and run a few test commands.

# Demo Video

Timestamps

  • 0:28 – Adding the –no-tty flag to fix it
  • 0:57 – Using a script to dynamically add this flag if needed
  • 1:21 – Quick demo of the psql shortcut
  • 2:04 – Checking to see if a TTY is available
  • 2:31 – Producing the error by modifying the script

Are you doing this in your scripts? Let me know below.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per year (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments