Skip to main content
Summary

Docker를 배우다 보면 컨테이너가 예상대로 실행되지 않는 순간을 자주 만난다. 웹 서버가 열리지 않거나, 데이터베이스 연결이 실패하거나, 분명히 코드를 고쳤는데 결과가 바뀌지 않는 일이 생긴다. 이때 중요한 것은 명령어를 더 많이 외우는 것이 아니라 문제를 좁혀 가는 순서를 익히는 것이다.

이번 장에서는 로그와 상태 확인 명령을 사용해 원인을 찾는 방법을 다룬다. 실무에서 Docker를 잘 다룬다는 것은 컨테이너를 실행하는 능력만 뜻하지 않는다. 컨테이너가 실패했을 때 어디를 봐야 하는지 아는 능력이 더 중요하다.

로그, 디버깅, 트러블슈팅AI 생성 이미지

먼저 상태를 본다

문제가 생기면 가장 먼저 컨테이너가 살아 있는지 확인한다.

docker ps

이 명령은 실행 중인 컨테이너만 보여 준다. 방금 실행한 컨테이너가 목록에 없다면 이미 종료되었을 수 있다. 종료된 컨테이너까지 보려면 -a를 붙인다.

docker ps -a

Compose 프로젝트라면 다음 명령을 사용한다.

docker compose ps

종료된 컨테이너까지 함께 확인하고 싶다면 다음처럼 -a를 붙여 본다.

docker compose ps -a

여기서 확인할 것은 세 가지다.

  • 컨테이너가 Up 상태인가?

  • Exited라면 종료 코드가 무엇인가?

  • 포트가 원하는 대로 게시되어 있는가?

예를 들어 Exited (1)은 애플리케이션 내부 오류로 종료된 경우가 많다. Exited (0)은 명령을 정상 실행한 뒤 종료되었다는 뜻이다. 웹 서버처럼 계속 떠 있어야 하는 컨테이너가 Exited (0)이라면 시작 명령이 장기 실행 프로세스가 아닐 가능성이 있다.

로그로 원인을 찾는다

컨테이너 문제를 볼 때 가장 많이 쓰는 명령은 docker logs다.

docker logs 컨테이너이름

실시간으로 로그를 따라가려면 -f를 붙인다.

docker logs -f 컨테이너이름

최근 로그만 보고 싶을 때는 --tail이 유용하다.

docker logs --tail 100 컨테이너이름

Compose에서는 서비스 단위로 로그를 볼 수 있다.

docker compose logs

docker compose logs app

docker compose logs -f app

여러 서비스가 함께 실행되는 환경에서는 전체 로그를 먼저 보고, 문제가 의심되는 서비스로 좁혀 가면 된다. 예를 들어 웹 요청이 실패한다면 web의 Nginx 로그를 보고, 그다음 app 로그를 확인한다. 데이터베이스 연결 오류가 보이면 db 로그를 확인한다.

로그를 읽을 때는 마지막 줄만 보지 말고, 실패 직전의 흐름을 함께 본다. connection refused, permission denied, address already in use, no such file or directory 같은 문구는 원인을 빠르게 좁혀 주는 단서다.

inspect로 설정을 확인한다

로그는 애플리케이션이 출력한 정보다. Docker가 컨테이너를 어떤 설정으로 실행했는지 보려면 docker inspect를 사용한다.

docker inspect 컨테이너이름

출력은 길다. 처음에는 전부 이해하려고 하지 않아도 된다. 다음 항목을 중심으로 보면 된다.

  • State: 실행 상태, 종료 코드, 시작·종료 시간

  • Config.Env: 컨테이너에 전달된 환경 변수

  • NetworkSettings: IP 주소, 연결된 네트워크

  • Mounts: 연결된 볼륨과 바인드 마운트

  • HostConfig.PortBindings: 호스트 포트와 컨테이너 포트 연결

특정 값만 보고 싶다면 Go template 형식을 사용할 수 있다.

docker inspect -f '{{.State.Status}}' 컨테이너이름

docker inspect -f '{{.State.ExitCode}}' 컨테이너이름

Compose에서는 서비스 설정이 최종적으로 어떻게 합쳐졌는지 확인하는 명령도 중요하다.

docker compose config

여러 Compose 파일, .env, override 파일을 사용한다면 실제 적용된 설정이 머릿속 예상과 다를 수 있다. 이 명령으로 최종 설정을 먼저 확인하면 불필요한 추측을 줄일 수 있다.

events로 Docker의 움직임을 본다

docker events는 Docker Engine에서 발생하는 이벤트를 실시간으로 보여 준다.

docker events

다른 터미널에서 컨테이너를 실행하거나 중지해 보면 create, start, die, destroy 같은 이벤트가 출력된다. 컨테이너가 반복해서 재시작되는 상황에서는 이 명령이 특히 도움이 된다.

docker events --filter container=컨테이너이름

일반적인 개발에서는 logs, ps, inspect만으로 충분한 경우가 많다. 하지만 Docker가 컨테이너를 실제로 어떻게 만들고 멈추는지 시간 순서로 확인해야 할 때는 events를 사용한다.

컨테이너 내부에서 확인하기

컨테이너가 실행 중이라면 내부 셸에 들어가 직접 확인할 수 있다.

docker exec -it 컨테이너이름 sh

이미지에 bash가 있으면 다음처럼 사용할 수도 있다.

docker exec -it 컨테이너이름 bash

가벼운 이미지에는 bash가 없는 경우가 많다. 이때는 sh를 먼저 시도한다.

컨테이너 안에 들어간 뒤에는 현재 위치, 파일 목록, 환경 변수를 차례로 확인해 본다.

pwd
ls
printenv

환경 변수 중 특정 값만 보고 싶다면 다음처럼 확인한다.

printenv DATABASE_URL
printenv NODE_ENV

네트워크 연결도 컨테이너 안에서 확인할 수 있다. 예를 들어 app 컨테이너가 db 서비스에 접근해야 한다면 localhost가 아니라 db로 접속해야 한다. Compose 네트워크 안에서 localhost는 보통 자기 자신, 즉 현재 컨테이너를 가리키기 때문이다.

docker compose exec app sh

컨테이너 내부에서 다음처럼 확인한다.

getent hosts db

이미지에 도구가 설치되어 있다면 HTTP 요청도 테스트할 수 있다.

wget -qO- http://web
wget -qO- http://app:3000

단, 모든 이미지에 curl, wget, ping이 들어 있는 것은 아니다. 운영용 이미지는 일부러 도구를 줄여 작게 만드는 경우가 많다. 그럴 때는 같은 네트워크에 임시 디버그 컨테이너를 붙여 확인할 수 있다.

docker run --rm -it --network 프로젝트명_default alpine sh

들어간 뒤 필요한 도구를 설치해 테스트한다.

apk add --no-cache curl
curl http://app:3000

Compose 프로젝트의 네트워크 이름은 보통 프로젝트명_default 형식이다. 정확한 이름은 다음 명령으로 확인한다.

docker network ls

프로젝트 이름을 잘 모르겠다면 docker compose ls로 현재 Compose 프로젝트 목록을 먼저 확인해도 좋다.

docker compose ls

자주 만나는 문제 1: 포트 충돌

포트 충돌은 Docker 입문자가 가장 자주 만나는 문제다. 대표적인 오류 메시지는 다음과 비슷하다.

Bind for 0.0.0.0:8080 failed: port is already allocated

호스트의 8080 포트를 이미 다른 프로세스나 컨테이너가 사용 중이라는 뜻이다. 컨테이너 내부 포트가 아니라 호스트 포트가 충돌한다는 점이 중요하다.

먼저 실행 중인 컨테이너의 포트를 확인한다.

docker ps

Compose 프로젝트가 여러 개라면 다음도 확인한다.

docker compose ps

전체 Docker 환경에서 어떤 컨테이너가 포트를 쓰고 있는지 보고 싶다면 docker ps가 더 직접적이다. Compose 프로젝트 안에서만 보면 다른 프로젝트나 직접 실행한 컨테이너를 놓칠 수 있기 때문이다.

해결 방법은 둘 중 하나다. 기존 컨테이너를 중지하거나, 새 컨테이너의 호스트 포트를 바꾼다.

docker stop 기존컨테이너

또는 Compose 파일에서 왼쪽 포트를 바꾼다.

ports:
  - 8081:80

8081:80에서 8081은 호스트 포트, 80은 컨테이너 포트다. 브라우저에서는 http://localhost:8081로 접속한다.

자주 만나는 문제 2: 컨테이너가 즉시 종료됨

컨테이너가 실행되자마자 사라진다면 먼저 종료된 컨테이너 목록을 본다.

docker ps -a

Compose 프로젝트라면 다음처럼 확인한다.

docker compose ps -a

그다음 로그를 확인한다.

docker logs 컨테이너이름

Compose에서는 서비스 이름으로 로그를 볼 수 있다.

docker compose logs app

대표 원인은 다음과 같다.

  • 실행 명령이 잘못되었다.

  • 필요한 환경 변수가 없다.

  • 애플리케이션 파일이 이미지 안에 없다.

  • 데이터베이스 연결 실패 후 애플리케이션이 종료된다.

  • 웹 서버가 아닌 일회성 명령을 실행했다.

Compose에서는 서비스의 command나 entrypoint를 의심해야 할 때가 있다.

docker compose config

최종 설정에서 command가 예상대로 들어갔는지 확인한다. npm run dev를 실행해야 하는데 npm test가 들어가 있거나, 작업 디렉터리가 맞지 않아 파일을 찾지 못하는 경우도 흔하다.

자주 만나는 문제 3: 이미지 빌드 실패

빌드 실패는 대부분 Dockerfile, 빌드 컨텍스트, 의존성 설치 단계에서 발생한다.

docker build -t my-app .

Compose 빌드라면 다음처럼 실행한다.

docker compose build

캐시 때문에 원인을 놓치는 느낌이 들면 캐시 없이 빌드한다.

docker build --no-cache -t my-app .

docker compose build --no-cache

자주 확인할 부분은 다음과 같다.

  • COPY 대상 파일이 빌드 컨텍스트 안에 있는가?

  • .dockerignore가 필요한 파일까지 제외하고 있지 않은가?

  • package.json, requirements.txt 같은 의존성 파일이 먼저 복사되는가?

  • 베이스 이미지의 패키지 관리자가 맞는가?

  • 빌드 중 필요한 환경 변수나 인증 정보에 의존하고 있지 않은가?

예를 들어 COPY package*.json ./ 단계에서 실패한다면 현재 디렉터리에 해당 파일이 없거나, 다른 위치에서 빌드를 실행했을 가능성이 크다. Dockerfile이 있는 위치와 docker build 명령의 마지막 인자인 빌드 컨텍스트를 함께 확인해야 한다.

자주 만나는 문제 4: 권한 문제

권한 문제는 바인드 마운트와 볼륨을 사용할 때 자주 나타난다.

permission denied

컨테이너 내부 프로세스가 파일을 읽거나 쓸 권한이 없다는 뜻이다. 특히 Linux 환경에서는 호스트 파일의 소유자와 컨테이너 내부 사용자 UID가 맞지 않아 문제가 생길 수 있다.

먼저 마운트가 어떻게 연결되었는지 확인한다.

docker inspect 컨테이너이름

Mounts 항목에서 호스트 경로와 컨테이너 경로를 본다. 그다음 컨테이너 안에서 파일 권한을 확인한다.

docker exec -it 컨테이너이름 sh
ls -la

개발 환경에서는 임시로 권한을 조정해 원인을 확인할 수 있다. 하지만 운영 환경에서는 무조건 chmod 777로 해결하면 안 된다. 쓰기 권한을 필요한 디렉터리로 제한하고, 애플리케이션을 root가 아닌 사용자로 실행하는 방향이 더 안전하다.

오래된 볼륨과 캐시 정리하기

가끔 Compose 파일과 코드를 고쳤는데도 예전 데이터가 계속 남아 문제가 재현된다. 데이터베이스 스키마가 바뀌었는데 이전 볼륨의 데이터가 그대로 남아 있거나, node_modules 볼륨에 오래된 의존성이 남아 있는 경우다.

먼저 어떤 볼륨이 있는지 확인한다.

docker volume ls

Compose 프로젝트를 내리되 볼륨은 유지하려면 다음을 사용한다.

docker compose down

볼륨까지 삭제하려면 -v를 붙인다.

docker compose down -v

이 명령은 데이터베이스 데이터도 삭제할 수 있다. 따라서 실행 전에 정말 지워도 되는 개발용 데이터인지 확인해야 한다. 운영 데이터가 연결된 환경에서 습관적으로 사용하면 위험하다.

사용하지 않는 Docker 객체를 정리할 수도 있다.

docker system prune

사용하지 않는 볼륨까지 정리하려면 다음 명령을 사용한다.

docker system prune --volumes

이 역시 신중하게 실행해야 한다. 안전한 순서는 docker ps -a, docker volume ls, docker compose ls로 현재 상태를 확인한 뒤 삭제하는 것이다.

작은 실습: 문제를 순서대로 좁히기

간단한 Compose 환경에서 문제를 찾는 연습을 해 보자. compose.yaml을 다음처럼 작성한다.

services:
  app:
    image: nginx:alpine
    ports:
      - 8080:80

실행한다.

docker compose up -d

브라우저에서 http://localhost:8080에 접속한다. 정상적으로 Nginx 화면이 보이면 일부러 포트 충돌을 만들어 보자. 같은 디렉터리에서 같은 Compose 프로젝트를 다시 실행하면 보통 기존 컨테이너를 재사용하거나 재생성하므로 포트 충돌이 나지 않을 수 있다. 충돌을 확실히 보려면 다른 터미널에서 다음처럼 같은 호스트 포트로 별도 컨테이너를 실행해 본다.

docker run --rm -p 8080:80 nginx:alpine

이미 Compose의 app 서비스가 8080 포트를 사용하고 있다면 포트가 이미 할당되었다는 오류가 발생한다.

문제가 생기면 다음 순서로 확인한다.

docker compose ps

docker compose logs app

docker compose config

docker ps

이 순서가 중요하다. 상태를 보고, 로그를 보고, 설정을 보고, 전체 Docker 환경을 본다. 처음부터 파일을 무작정 고치면 원인을 놓치기 쉽다.

정리가 끝나면 다음 명령을 실행한다.

docker compose down

실습 중 별도 터미널에서 실행한 docker run --rm -p 8080:80 nginx:alpine 명령이 아직 떠 있다면 Ctrl+C로 종료한다.

트러블슈팅의 기본 순서

Docker 문제를 만났을 때는 다음 순서를 기억하자.

  1. docker ps -a 또는 docker compose ps -a로 상태를 본다.

  2. docker logs 또는 docker compose logs로 실패 메시지를 찾는다.

  3. docker inspect와 docker compose config로 실제 설정을 확인한다.

  4. 실행 중인 컨테이너에 exec로 들어가 파일, 환경 변수, 네트워크를 확인한다.

  5. 포트, 볼륨, 권한, 빌드 캐시처럼 외부 요인을 점검한다.

  6. 삭제가 필요할 때는 데이터 손실 범위를 확인한 뒤 정리한다.

컨테이너는 격리되어 있지만 문제 해결 방식은 단순하다. 실행 상태, 로그, 설정, 내부 환경을 차례로 확인하면 대부분의 원인은 좁혀진다. 다음 장에서는 이 흐름을 테스트와 CI 환경으로 확장해, Docker를 로컬 개발뿐 아니라 자동화된 검증 과정에서도 활용하는 방법을 다룬다.

책 목록

Docker와 Docker Compose 입문: 컨테이너 개발 환경 구축부터 운영 준비까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기