Docker run 시 ‘The input device is not a TTY’ 에러 — Jenkins/CI에서 -it 옵션 문제 해결

1. 문제 정의

Jenkinsfile(또는 cron·스크립트)에서 다음과 같은 docker run 명령을 실행하면 에러가 발생합니다.

1
docker run -v $PWD:/foobar -it cloudfoundry/cflinuxfs2 /foobar/script.sh

실제 원문은 Jenkinsfile 안에서 이 명령을 실행했을 때 아래와 같은 메시지가 났다는 질문입니다.

1
The input device is not a TTY
  • 증상 fingerprint
    • 에러 메시지: The input device is not a TTY (호환성 단락 참고: 최신 Docker 버전은 문구가 바뀜)
    • 발생 단계: CI(Jenkins)·cron 등 대화형 터미널이 없는 환경에서 docker run -it을 실행하는 순간
    • 관련 도구: Docker CLI, -it(-i --interactive + -t --tty) 옵션
    • 흔한 오해: “컨테이너가 고장났다” 또는 “이미지가 잘못됐다"고 생각하지만, 실제로는 실행 쪽 명령줄 옵션(-t)이 원인인 경우가 대부분입니다.
    • 빠른 판단 기준: 로컬 터미널에 직접 입력해도 같은 에러가 나는지 먼저 확인합니다. 로컬 터미널에서는 정상 동작하는데 CI에서만 난다면 TTY 부재가 확실한 원인입니다.

검증 환경 다르게 받아들이기 주의: 아래 3~5절의 “실행 결과"는 제가 실제로 실행해 측정한 값입니다. -t 옵션을 요구하는 명령이 TTY가 없는 비대화형 셸에서 거부되는 근본 원리(“stdin에 TTY가 없는데 -t로 TTY를 요구함”)는 Docker 버전에 걸쳐 일관되지만, 에러 문구·동작 세부는 Docker 버전과 호스트 터미널에 따라 다릅니다. 이 글의 재현 값은 뒤의 “검증 환경” 표에서 밝힌 특정 환경에서의 것임을 유의하세요.

2. 먼저 확인할 것

해결책을 고르기 전에, 아래를 순서대로 확인하세요.

  1. 실행 환경에 터미널이 있는가? — Jenkins, GitHub Actions, cron, SSH의 명령 실행(non-interactive shell)은 TTY를 제공하지 않습니다. [ -t 0 ]로 stdin이 TTY인지 확인할 수 있습니다.
  2. -it 옵션이 진짜 필요한가? — 스크립트(예: script.sh)를 실행만 하면 되는 경우 대화형 셸이 필요 없습니다.
  3. stdin에 입력을 파이프/리다이렉트하는가? — xyz | docker ... 또는 docker ... < input 형태라면 -i만 있으면 됩니다.
  4. docker run인가, docker compose exec인가? — compose exec라면 해결책이 다릅니다 (-T).

3. 원인별 분기표

내 상황원인적합한 해결책
CI/스크립트에서 script.sh만 실행, 대화형 불필요TTY 없음 + 상호작용 불필요-it 제거 (비대화형)
xyz | docker ... / docker ... < input 으로 stdin 입력 제공입력이 TTY가 아니라 파이프-it → -i
TTY 부재지만 로그에서 컬러/프롬프트(TTY 감지 앱) 필요TTY 없음 + TTY 감지 필요-it → -t(단, 입력을 파이프로 줄 때는 부적합)
docker compose exec 사용 중compose exec가 기본으로 pseudo-TTY 할당exec에 -T 추가
대화형 터미널이 반드시 필요하고 Windows 호스트실행 환경이 TTY 미지원(mintty 등)PowerShell 등 TTY 지원 CLI로 변경

4. 원인 후보와 배제 과정

에러를 보고 “컨테이너/이미지 문제"로 단정하지 않도록, 후보 원인을 하나씩 배제하는 논리입니다.

원인 후보확인 방법(명령)맞을 때의 증상배제/채택 논거
컨테이너가 손상됨docker run --rm <image> true (비대화형)아무것도 안 뜨고 0으로 종료배제 — 같은 이미지를 -it 없이 실행하면 정상 동작하므로 컨테이너 문제가 아님
이미지가 잘못됨docker pull <image> + docker run --rm <image> --version이미지 실행 자체가 실패배제 — 세션에서 해당 이미지가 실행될 때 오류가 -it 여부로만 달라짐
실행 환경(CI 에이전트)에 TTY 없음`[ -t 0 ] && echo TTYecho NO_TTY`
불필요한 -it(특히 -t)docker run -it <image> <cmd> vs docker run <image> <cmd>-t 포함 시 에러, 제거 시 성공채택(핵심 원인) — 실행만 하면 되는 스크립트에 -t가 불필요하게 붙음
stdin에 파이프를 주는데 -t까지 사용echo x | docker run -it alpine catstdin이 파이프인데 TTY 요구 → 에러채택 — 파이프/리다이렉트 입력은 -i로 충분, -t는 제거

배제 요약: “컨테이너 손상"과 “이미지 문제"는 같은 이미지·컨테이너를 비대화형(-it 없이)으로 재실행했을 때 성공하는지로 배제됩니다. 에러가 -t 유무에 따라서만 발생/소멸한다면 원인은 이미지가 아니라 실행 환경의 TTY 부재 + 불필요한 -t 입니다. 이 배제 논리는 Docker 버전과 관계없이 공통입니다.

5. 상황별 해결책

5-1. 대화형이 필요 없을 때 — -it 제거 (가장 흔한 경우)

Jenkinsfile에서 그냥 스크립트를 실행하고 싶다면 -it를 지우면 됩니다.

1
2
3
4
5
# 수정 전 — 에러 발생
docker run -v $PWD:/foobar -it cloudfoundry/cflinuxfs2 /foobar/script.sh

# 수정 후 — 비대화형으로 실행
docker run -v $PWD:/foobar cloudfoundry/cflinuxfs2 /foobar/script.sh

5-2. stdin에 입력을 파이프로 줄 때 — -i 사용

입력을 명령줄이 아니라 파이프/리다이렉트로 넘기는 경우 -t는 필요 없고 -i만 남기면 됩니다.

1
2
3
4
5
# 파이프로 입력을 넘기는 경우
xyz | docker run -i --rm alpine sh

# 리다이렉트로 입력을 넘기는 경우
docker run -i --rm alpine sh < input.txt

5-3. TTY 감지(컬러 출력 등)만 필요할 때 — -t 사용

컨테이너 안 앱이 “TTY가 있으면 컬러로 출력"처럼 TTY 존재를 검사하는데, 입력 디바이스에 TTY가 없을 때 -t만 사용합니다. 나중에 docker attach로 진입할 계획이 있을 때도 -t가 적합합니다. 단, stdin에 파이프를 주면서 -t를 쓰면 다시 에러가 나므로 그 경우엔 -i를 씁니다.

1
docker run -t --rm myapp --color

5-4. docker compose exec일 때 — -T 사용

docker compose exec(및 구형 docker-compose exec)는 기본적으로 pseudo-TTY를 할당하므로, CI에서는 -T로 이를 끕니다.

1
2
3
4
5
# 예: postgres 컨테이너에서 백업 스크립트 실행
docker compose exec -T postgres backup

# 예: MySQL 덤프를 리다이렉트로 넣기
docker compose exec -T mysql mysql -uuser -ppassword db < db_backup.sql

선택 이유 요약: 해결책이 하나로 고정된 것이 아니라 “입력 TTY가 필요한지 / TTY 감지가 필요한지 / stdin을 어떻게 주는지"에 따라 달라집니다. 4절 분기표가 선택의 기준입니다. (-it = -i + -t라는 사실이 헷갈림의 주 원인입니다.)

6. 버전별 차이

에러가 “차이가 없는 단일 케이스"로 보이기 쉽지만, 실제로는 Docker 버전·Compose 버전·CI 에이전트 종류에 따라 문구와 권장 명령이 달라집니다. (본 절 내용은 아래 “검증 환경” 표의 Docker 29.5.2 / Compose v5.1.4 측정 + 각 버전 공식 문서 기준입니다.)

구분특징TTY 에러 관련 차이
구형 Docker CLI (질문 시점 2017년경, 17.x)빌트인 CLI의 초기 시절에러 문구가 The input device is not a TTY 로 출력됨 (질문 원문과 동일)
최신 Docker CLI (본 재현: 29.5.2)문구가 친절하게 개편됨문구가 cannot attach stdin to a TTY-enabled container because stdin is not a terminal 로 바뀌었지만, 동작은 동일하게 TTY 요구를 거부(종료 코드 1)함
docker-compose v1 (하이픈, 독립 실행형 Python 바이너리)2022년 deprecated, 공식 배포 채널에선 2025년 4월 이후 제거resolve 명령은 docker-compose exec -T ... — -T(pseudo-TTY 비활성화) 플래그를 사용
docker compose v2 (플러그인/서브커맨드, Go 기반)현재 표준. v5.x까지 -T 지원docker compose exec -T — --help에 -T, --no-tty Disable pseudo-TTY allocation 확인됨
로컬 터미널에서 stdin을 파이프로 넘길 때GUI 터미널(mintty/Git Bash 등)에서 발생구형 문구에 “If you are using mintty, try prefixing with ‘winpty’” 안내가 붙기도 함 → PowerShell/winpty로 전환
Jenkins 에이전트sh 스텝·cron은 보통 TTY 없음Jenkins 선언적 파이프라인의 sh 및 원격 에이전트 over SSH는 기본적으로 pseudo-TTY를 할당하지 않으므로 -it 사용 시 에러

결론 정리: 문구는 다르지만 근본 원리는 같다. 그리고 -T 플래그는 docker-compose(v1), docker compose(v2) 모두 지원하므로, v1이냐 v2냐와 무관하게 compose exec에는 -T를 쓰면 됩니다. 만약 레거시 docker-compose(하이픈)를 쓰는 환경이라면 공식 배포 채널에서 바이너리가 제거되었으므로 v2(docker compose)로 마이그레이션하는 것을 권장합니다.

7. 재현 절차와 실제 실행 결과 (실측)

아래 결과는 이 글을 작성하면서 실제로 실행해 측정한 값입니다. 로컬 셸은 stdin is NOT a TTY (non-interactive) 상태임을 확인했습니다.

1
2
3
# 0) 사실 확인 — 실행 셸에 TTY가 있는가?
[ -t 0 ] && echo "stdin IS a TTY" || echo "stdin is NOT a TTY (non-interactive)"
# → stdin is NOT a TTY (non-interactive)

재현 1 (에러 재현 — -it 사용):

1
sudo docker run -v "$PWD":/foobar -it alpine /foobar/script.sh; echo "exit_code=$?"

실제 출력:

1
2
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
exit_code=1

재현 2 (해결 — -it 제거 후 비대화형 실행):

1
sudo docker run --rm -v "$PWD":/foobar alpine /foobar/script.sh; echo "exit_code=$?"

실제 출력 (script.sh는 컨테이너 안에서 echo를 수행하는 파일):

1
2
3
script.sh executed inside container
date: 2026-09-13
exit_code=0

재현 3 (docker compose exec -T 검증):

1
2
sudo docker compose up -d
sudo docker compose exec -T app sh -c 'echo "OK: exec -T works"; date -u +%F'; echo "exit_code=$?"

실제 출력:

1
2
3
4
5
Network tty-repro_default Created
Container tty-repro-app-1  Started
OK: exec -T works
2026-09-13
exit_code=0

검증 명령(정리):

1
2
3
4
5
# 비대화형 docker run이 정상 종료(exit 0)되는지
docker run --rm -v "$PWD":/foobar <image> /foobar/script.sh && echo "OK: non-interactive run succeeded"

# docker compose exec -T 검증
docker compose exec -T <service> <cmd> && echo "OK: exec -T works"

검증 환경 (실측 기준)

항목값
호스트 OS / CPULinux 6.8.0-1049-oracle, aarch64 (arm64)
실행 셸비대화형 셸 (stdin is NOT a TTY) — Jenkins sh와 유사
Docker Engine (client/server)29.5.2 (API 1.54)
Docker Composev5.1.4 (docker compose 서브커맨드)
docker-compose v1 (하이픈)미설치 (요즘 배포판 기본)
이미지alpine (스크립트 볼륨 마운트)

환경에 따라 다를 수 있음: 위 문구(cannot attach stdin...)와 실행 결과는 위 표의 특정 환경 기준입니다. 여러분의 CI 에이전트·Docker 버전이 다르면 문구가 The input device is not a TTY처럼 보일 수 있고, 매우 구형 Docker에서는 미세한 동작 차이가 있을 수 있습니다. 근본 원리(stdin TTY 부재 + 불필요한 -t)와 해결 방향(-it 제거 또는 -T)은 모든 버전에서 동일합니다.

8. 예방 조치 (운영 재발 방지)

  • Jenkinsfile/스크립트에서는 가급적 -d(백그라운드) 또는 비대화형 실행을 기본으로 정합니다.
  • -it는 로컬에서 직접 디버깅할 때만 쓰고, CI 파이프라인에는 -it를 넣지 않도록 컨벤션(코드 리뷰/스니펫)으로 관리합니다.
  • 같은 파이프라인에서 docker compose exec를 쓸 땐 -T를 습관처럼 붙입니다.
  • 잘못된 해결책(주의): TTY 에러를 억제하려고 2>/dev/null로 에러만 감추거나, 억지로 script -q -c "..." 같은 우회를 도입하는 것은 증상만 가리고 근본 원인(불필요한 -it)을 남깁니다. 옵션을 바로 잡는 것이 근본 해결입니다.
  • CI 셸이 TTY를 못 만들 때 에러를 사전에 회피하려면 if [ -t 0 ]; then DOCKER_OPTS=-it; else DOCKER_OPTS=; fi 같은 분기를 넣어 “로컬에선 대화형, CI에선 비대화형"으로 동작하게 만들 수 있습니다(일반적인 패턴).

DevTrace 결론

이 문제의 핵심은 “컨테이너가 아니라 실행 환경에 대화형 터미널(TTY)이 없다“는 점이며, CI 에이전트(비대화형 셸)에서는 -it의 -t가 불필요해 에러를 만듭니다. 컨테이너·이미지 문제는 비대화형 재실행으로 배제할 수 있고, -it → 제거/-i/-t/compose -T 중 상황에 맞는 옵션을 고르면 해결됩니다. TTY란 이스케이프 시퀀스·커서 이동 등을 지원하는 터미널 인터페이스로, 오늘날에는 리눅스 셸과 SSH가 제공하며 CI 에이전트는 기본적으로 이를 갖지 않습니다.


출처: StackOverflow — Error “The input device is not a TTY” (question 43099116) · Wikipedia — Terminal emulator (TTY) · Docker Compose v2의 exec -T 동작은 Compose CLI --help 및 공식 문서 기준, docker-compose v1 제거는 공식 설치 문서(standalone legacy) 기준입니다. 재현·실측 값(7절)은 본 문서의 “검증 환경” 표 기준이며, Docker·Compose 버전에 따라 에러 문구가 다를 수 있습니다.