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

We'll get things to dynamically work with stdin and stdout and allow reusing Docker Compose commands when a TTY is available.
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.