docker ps를 볼 때마다 컨테이너가 Restarting으로 바뀐다면 먼저 마지막 로그를 남겨 두세요. 시작 명령이 실패했는지, 메모리가 부족했는지, 필요한 파일이 없는지에 따라 고칠 곳이 달라집니다. 재시작 정책은 종료된 컨테이너를 다시 띄워 줄 뿐, 실패한 이유까지 해결해 주지는 않아요.
아래는 Linux의 Docker Engine과 Docker Compose v2 기준입니다. myapp은 컨테이너 이름, app은 Compose 서비스 이름의 예시입니다. 두 이름이 항상 같은 것은 아니므로 docker compose ps -a에서 먼저 확인합니다.
이 글의 목차
실패한 시점의 상태와 로그를 먼저 남깁니다
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker logs --timestamps --tail 100 myapp
docker inspect myapp --format '{{json .State}}'
docker inspect myapp --format 'restarts={{.RestartCount}} policy={{.HostConfig.RestartPolicy.Name}}'
State에서 ExitCode, OOMKilled, Error, StartedAt, FinishedAt을 함께 봅니다. 컨테이너가 다시 실행되는 순간에는 현재 상태가 바뀌므로, 같은 시각의 로그와 대조해야 합니다. RestartCount가 계속 증가하는지도 확인하세요.
로그가 비어 있어도 정상이라는 뜻은 아닙니다. 프로그램이 표준 출력·표준 오류 대신 내부 파일에 기록하거나, 실행 파일을 시작하기 전에 실패할 수 있어요. 이때는 State.Error와 Docker 데몬 로그를 확인합니다. 조사 자료를 공유할 때에는 로그의 토큰·연결 문자열 등 비밀 값을 가려야 합니다.
| 단서 | 해석과 다음 확인 |
|---|---|
ExitCode=0인데 반복 실행 | 주 프로세스가 정상 종료되는 단발성 작업인지, always 정책인지 확인 |
| 0이 아닌 종료 코드와 설정 오류 로그 | 필수 설정·환경 변수·실행 명령 확인. 코드 의미는 프로그램 문서와 대조 |
OOMKilled=true | 컨테이너 메모리 제한과 호스트의 메모리 상태 확인 |
| 종료 코드 137 | SIGKILL과 관련된 단서. OOM 여부는 상태와 커널 로그로 추가 확인 |
unhealthy지만 계속 실행 중 | 건강 검사 실패 조사. 일반 Docker 재시작 정책은 이 상태만으로 재시작하지 않음 |
재시작 반복을 멈춰야 할 때
외부 시스템에 요청을 반복하거나 로그가 빠르게 쌓이면, 영향을 확인한 뒤 조사 대상 컨테이너만 멈춥니다. 다음 명령은 해당 컨테이너의 서비스를 중단합니다. 다른 서비스까지 함께 내리는 docker compose down을 진단 첫 단계로 사용할 필요는 없습니다.
docker stop myapp
수동으로 중지하면 일반적인 Docker 재시작 정책은 당장 컨테이너를 다시 시작하지 않습니다. 다만 always 정책은 Docker 데몬이 다시 시작될 때의 동작이 다릅니다. Kubernetes·Swarm·외부 감시 프로그램이 관리하는 컨테이너라면 그 관리 주체에서 중지·변경해야 합니다.
환경 변수보다 실행 명령과 마운트를 먼저 맞춰봅니다
docker inspect myapp --format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}'
docker inspect myapp --format '{{json .Mounts}}'
docker compose config --quiet
이미지의 기본 명령을 Compose의 command로 덮어썼는지 확인합니다. 실행 파일 경로, 인수, 작업 디렉터리가 달라지면 설치가 정상이어도 시작이 실패할 수 있어요. 명령 인수에 비밀 값을 넣은 환경에서는 위 출력에도 포함될 수 있으므로 외부에 그대로 붙여 넣지 않습니다.
마운트에서는 Source, Destination, RW를 확인합니다. 호스트의 빈 디렉터리가 이미지 안의 설정 디렉터리를 덮거나, 쓰기가 필요한 경로가 읽기 전용으로 연결됐는지 살펴보세요. 환경 변수는 Compose 파일과 프로그램 문서를 대조하되 전체 설정을 공개 로그로 출력하지 않는 편이 좋습니다.
Permission denied가 보이면 컨테이너 실행 사용자와 호스트 디렉터리의 소유권을 맞춰 봅니다. Rocky Linux에서는 SELinux의 거부 기록도 확인합니다. 원인을 확인하지 않고 chmod -R 777을 적용하거나 SELinux를 끄면 문제가 생긴 지점을 가리기 쉽습니다. 기존 데이터가 있는 볼륨에는 소유권을 일괄 변경하기 전 영향 범위를 확인합니다.
메모리 종료와 의존 서비스 실패를 구분합니다
docker inspect myapp --format 'oom={{.State.OOMKilled}} memory_limit={{.HostConfig.Memory}}'
docker stats --no-stream myapp
free -h
sudo journalctl -k --since '30 minutes ago'
HostConfig.Memory는 바이트 단위이며 0은 컨테이너에 별도 메모리 상한을 지정하지 않았다는 뜻입니다. 호스트나 상위 cgroup의 제한까지 없다는 뜻은 아닙니다. docker stats는 실행 중인 순간의 값이므로 이미 종료된 직전의 최고 사용량은 보여주지 못합니다.
로그가 데이터베이스 연결 실패를 가리킨다면 주소·포트·인증·준비 상태를 따로 확인합니다. Compose의 단순한 depends_on만으로 데이터베이스가 요청을 받을 준비까지 끝났다고 가정하지 않습니다. 건강 검사 조건과 애플리케이션의 재시도 동작을 함께 설계해야 해요.
Compose 설정을 바꿨으면 컨테이너를 다시 생성합니다
환경 변수나 명령을 수정한 뒤 docker restart만 실행하면 기존 컨테이너의 설정으로 다시 시작합니다. Compose 파일을 관리하는 프로젝트 디렉터리에서 아래처럼 해당 서비스를 갱신합니다. 재생성 중에는 서비스 연결이 끊길 수 있습니다.
docker compose config --quiet
docker compose up -d --no-deps app
docker compose ps -a
docker compose logs --since 5m --tail 100 app
--no-deps는 의존 서비스를 함께 시작하지 않는 옵션입니다. 의존 서비스가 이미 정상 동작 중이고, 그 설정은 바꾸지 않았을 때 사용합니다. 이미지나 실행 파일 자체를 고쳤다면 이미지 빌드·배포 과정도 별도로 필요합니다. 데이터 볼륨을 지우는 down -v나 무차별 정리는 이 진단 과정에 필요하지 않습니다.
Up 표시 다음에는 실제 요청을 확인합니다
컨테이너가 Up으로 보이고 재시작 횟수가 늘지 않는지 확인합니다. 건강 검사가 있다면 그 결과를 보고, 애플리케이션에도 실제 요청을 보내세요. 컨테이너의 프로세스가 살아 있는 상태와 서비스가 정상 응답하는 상태는 따로 확인해야 합니다.
별도 Python 컨테이너에서 의도적으로 종료 코드 42를 반환하도록 구성해 재시작 횟수와 로그 조회를 확인했습니다. 이 실험은 상태를 읽는 방법을 확인한 것이며, 특정 데이터베이스의 장애나 메모리 고갈을 재현한 결과는 아닙니다. 호스트 서비스 로그가 필요하면 systemd와 journalctl 사용법도 함께 볼 수 있습니다.



