본문 바로가기
빌드노트 검색

빌드노트

compose exec 파이프 입력의 TTY 오류와 -T의 역할

compose exec의 -T는 stdin을 유지하고 TTY만 끈다. Compose v5.5.1에서 출력이 터미널일 때의 오류와 파이프로 수집할 때의 성공을 비교하고, --interactive=false로 입력까지 끄는 실수를 구분한다.

파이프로 데이터를 넘긴 docker compose exec가 stdin is not a terminal이라는 오류로 끝난다면, 실행 옵션의 -T부터 확인한다. -T는 stdin 전달을 유지하면서 pseudo-TTY 할당을 끈다. 입력이 필요한 자동화에서 --interactive=false로 바꾸면 정작 보내려던 데이터가 전달되지 않을 수 있다.

stdin은 프로그램이 입력 바이트를 읽는 통로다. pseudo-TTY는 프로그램이 터미널에 연결된 것처럼 동작하게 하는 장치다. 파이프에서 데이터를 읽는 데 터미널까지 필요한 것은 아니다. Docker의 Compose exec 문서 (새 탭에서 열림)도 interactive 모드와 TTY를 구분하고, -T를 TTY 할당을 끄는 옵션으로 설명한다.

입력은 남기고 TTY만 끄는 명령

다음 명령은 두 줄이 컨테이너에 도착하는지 확인한다. PowerShell 7에서 자신의 Compose 프로젝트 디렉터리로 이동하고, worker를 실행 중인 Linux 서비스 이름으로 바꾼다. 대상 이미지에는 head가 있어야 한다. 데이터베이스 가져오기 같은 실제 쓰기 명령 대신 고정 문자열만 읽는 확인이다.

'alpha', 'beta' | docker compose exec -T worker head -n 2

실습에서는 alpha, beta가 차례로 출력됐고 종료 코드는 0이었다. -T를 붙여도 입력은 사라지지 않았다. PowerShell에서 종료 코드는 명령 직후 $LASTEXITCODE로 확인할 수 있다. 이 예는 텍스트 두 줄의 전달을 보는 것이며, 바이너리 파일의 무손실 전송을 검사하는 코드는 아니다.

컨테이너 안의 명령이 터미널 입력을 받는지도 따로 확인할 수 있다. 다음 명령은 sh가 있는 같은 서비스에서 stdin의 터미널 여부를 출력한다.

docker compose exec -T worker sh -c 'test -t 0 && echo tty=yes || echo tty=no'

관찰값은 tty=no였다. 여기서 0은 stdin의 파일 디스크립터 번호다. 이 조회는 터미널 여부만 알려 준다. 실제 데이터가 도착했는지는 앞의 두 줄 출력처럼 별도로 확인해야 한다.

기본 명령이 성공하는 환경도 있었다

이번에는 기존 서비스를 사용하지 않고 네트워크가 none인 새 Compose 프로젝트에서 비교했다. 환경은 Windows 11, PowerShell 7.6.6, Docker Desktop 4.92.0의 Linux 엔진 29.8.0, Compose v5.5.1, BusyBox 1.37.0이다. 실제 장애 기록이 아니라 합성 입력으로 실행 경로를 확인한 실습이다.

입력은 LF로 끝나는 alpha와 beta, 총 11바이트로 고정했다. 컨테이너의 작은 셸 명령은 stdin의 터미널 여부를 출력하고 두 줄을 읽은 뒤, stderr에 diag 한 줄을 썼다. 입력이 같아도 Docker CLI의 stdout을 어디에 연결하느냐에 따라 기본 명령의 결과가 달랐다.

  • stdin은 파이프, stdout은 실제 터미널에 연결: 기본 exec는 종료 코드 1과 cannot attach stdin to a TTY-enabled container because stdin is not a terminal 오류를 반환했다. 같은 조건의 exec -T는 종료 코드 0으로 tty=no, 두 입력 줄과 diag를 출력했다.
  • stdin과 stdout을 모두 파이프로 연결해 바이트로 수집: 기본 exec와 exec -T 모두 종료 코드 0이었다. 둘 다 stdout은 b'tty=no\nalpha\nbeta\n'인 18바이트, stderr는 b'diag\n'인 5바이트였다. 두 명령 사이의 출력 바이트 차이는 없었다.
  • 같은 수집 조건에서 -T --interactive=false 사용: 종료 코드는 여전히 0이었지만 stdout은 b'tty=no\n'인 7바이트뿐이었다. alpha와 beta는 전달되지 않았다. 이 실습의 head는 빈 입력에도 성공하므로 종료 코드만으로 입력 전달을 판정할 수 없었다.

공식 사용 설명에는 기본 TTY 할당이라고 적혀 있지만, Compose v5.5.1의 exec 구현 (새 탭에서 열림)은 no-tty의 기본값을 CLI의 stdout이 터미널인지에 따라 정한다. stdout이 터미널이 아니면 TTY를 끄는 방향이다. 위 관찰은 이 구현과 일치한다. 입력을 파이프로 받는다는 사실만으로 모든 호출이 같은 오류를 낸다고 설명할 수는 없다.

자동화에서 유지할 조건을 정한다

파이프 입력이 필요한 작업에서는 stdin 연결을 유지하고 -T를 명시한다. 지금의 출력 수집 방식에서 기본 명령이 성공해도, 터미널에서 직접 실행하는 경로까지 같다고 가정하지 않는다. -T를 명시하면 출력 목적지의 자동 판별에 맡기지 않고 TTY 없는 실행을 선택할 수 있다.

--interactive=false는 입력이 필요 없는 명령에서 stdin 연결을 끌 때 선택할 옵션이다. SQL이나 설정 내용을 stdin으로 읽는 명령의 TTY 오류를 고치려고 함께 붙이지 않는다. 수정 뒤에는 종료 코드와 함께 소비한 입력이나 명령의 실제 결과도 확인한다.

-T를 붙인 뒤에도 실패하면 오류 원문을 다시 읽는다. 서비스가 실행 중이지 않거나 이미지에 head·sh가 없는 경우는 이 실습의 TTY 문제와 다르다. 두 줄이 나오지 않는다면 입력을 만드는 쪽의 실패와 대상 명령이 stdin을 읽는지도 확인한다.

직접 확인한 범위는 위 버전의 Linux 컨테이너와 짧은 텍스트 입력이다. 다른 Compose 버전의 자동 판별, 실제 데이터베이스 가져오기, TTY가 꼭 필요한 대화형 프로그램과 바이너리 입력은 검증하지 않았다. 터미널에 직접 연결한 출력은 화면으로 관찰했고, 바이트 수와 stdout·stderr 분리 비교는 파이프로 수집한 실행에 한정한다.

← 전체 글 보기