Docker run 시 ‘The input device is not a TTY’ 에러 — Jenkins/CI에서 -it 옵션 문제 해결
1. 문제 정의
Jenkinsfile(또는 cron·스크립트)에서 다음과 같은 docker run 명령을 실행하면 에러가 발생합니다.
| |
실제 원문은 Jenkinsfile 안에서 이 명령을 실행했을 때 아래와 같은 메시지가 났다는 질문입니다.
| |
- 증상 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. 먼저 확인할 것
해결책을 고르기 전에, 아래를 순서대로 확인하세요.
- 실행 환경에 터미널이 있는가? — Jenkins, GitHub Actions, cron, SSH의 명령 실행(non-interactive shell)은 TTY를 제공하지 않습니다.
[ -t 0 ]로 stdin이 TTY인지 확인할 수 있습니다. -it옵션이 진짜 필요한가? — 스크립트(예:script.sh)를 실행만 하면 되는 경우 대화형 셸이 필요 없습니다.- stdin에 입력을 파이프/리다이렉트하는가? —
xyz | docker ...또는docker ... < input형태라면-i만 있으면 됩니다. 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 TTY | echo NO_TTY` | |
불필요한 -it(특히 -t) | docker run -it <image> <cmd> vs docker run <image> <cmd> | -t 포함 시 에러, 제거 시 성공 | 채택(핵심 원인) — 실행만 하면 되는 스크립트에 -t가 불필요하게 붙음 |
stdin에 파이프를 주는데 -t까지 사용 | echo x | docker run -it alpine cat | stdin이 파이프인데 TTY 요구 → 에러 | 채택 — 파이프/리다이렉트 입력은 -i로 충분, -t는 제거 |
배제 요약: “컨테이너 손상"과 “이미지 문제"는 같은 이미지·컨테이너를 비대화형(-it 없이)으로 재실행했을 때 성공하는지로 배제됩니다. 에러가 -t 유무에 따라서만 발생/소멸한다면 원인은 이미지가 아니라 실행 환경의 TTY 부재 + 불필요한 -t 입니다. 이 배제 논리는 Docker 버전과 관계없이 공통입니다.
5. 상황별 해결책
5-1. 대화형이 필요 없을 때 — -it 제거 (가장 흔한 경우)
Jenkinsfile에서 그냥 스크립트를 실행하고 싶다면 -it를 지우면 됩니다.
| |
5-2. stdin에 입력을 파이프로 줄 때 — -i 사용
입력을 명령줄이 아니라 파이프/리다이렉트로 넘기는 경우 -t는 필요 없고 -i만 남기면 됩니다.
| |
5-3. TTY 감지(컬러 출력 등)만 필요할 때 — -t 사용
컨테이너 안 앱이 “TTY가 있으면 컬러로 출력"처럼 TTY 존재를 검사하는데, 입력 디바이스에 TTY가 없을 때 -t만 사용합니다. 나중에 docker attach로 진입할 계획이 있을 때도 -t가 적합합니다. 단, stdin에 파이프를 주면서 -t를 쓰면 다시 에러가 나므로 그 경우엔 -i를 씁니다.
| |
5-4. docker compose exec일 때 — -T 사용
docker compose exec(및 구형 docker-compose exec)는 기본적으로 pseudo-TTY를 할당하므로, CI에서는 -T로 이를 끕니다.
| |
선택 이유 요약: 해결책이 하나로 고정된 것이 아니라 “입력 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 (에러 재현 — -it 사용):
| |
실제 출력:
| |
재현 2 (해결 — -it 제거 후 비대화형 실행):
| |
실제 출력 (script.sh는 컨테이너 안에서 echo를 수행하는 파일):
| |
재현 3 (docker compose exec -T 검증):
| |
실제 출력:
| |
검증 명령(정리):
| |
검증 환경 (실측 기준)
| 항목 | 값 |
|---|---|
| 호스트 OS / CPU | Linux 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 Compose | v5.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 버전에 따라 에러 문구가 다를 수 있습니다.