Docker 컨테이너 갑자기 죽는 이유는 생각보다 단순하지 않습니다.
Docker 컨테이너 갑자기 죽는 이유를 제대로 보려면, 먼저 OOMKilled인지, 앱이 스스로 종료한 것인지, SIGTERM/SIGKILL로 내려간 것인지부터 구분해야 합니다.
특히 운영 환경에서는 Docker 컨테이너 갑자기 죽는 이유를 감으로 보면 안 되고, 종료 코드·로그·이벤트·호스트 자원 상태를 같이 봐야 원인을 빨리 찾을 수 있습니다. Docker는 기본적으로 컨테이너가 종료되면 종료 코드와 상태를 남기고, 리눅스는 메모리가 부족하면 OOM killer로 프로세스를 정리합니다.
먼저 결론부터: 컨테이너가 “갑자기” 죽는 대표 원인
Docker 컨테이너 갑자기 죽는 이유는 실무에서 보통 아래 네 가지로 압축됩니다.
첫째, 애플리케이션 프로세스가 오류로 스스로 종료한 경우입니다. 둘째, 메모리 부족으로 OOMKilled가 난 경우입니다. 셋째, 배포·재시작·수동 정지 과정에서 SIGTERM 후 SIGKILL로 종료된 경우입니다. 넷째, DB 같은 의존 서비스가 아직 준비되지 않았는데 앱이 먼저 올라와 바로 실패하는 경우입니다. Docker Compose는 기본적으로 “실행 중(running)”까지만 보고 “준비 완료(ready)”까지 기다리지는 않기 때문에 이 문제가 자주 발생합니다.
OOMKilled가 정확히 무엇인가
OOMKilled는 리눅스 커널이 메모리를 더 확보할 수 없을 때 프로세스를 강제로 정리하는 상황입니다. Docker 문서도 리눅스 호스트에서 메모리가 부족해지면 OOME가 발생하고, 프로세스를 죽여서 메모리를 확보한다고 설명합니다. Docker는 데몬이 먼저 죽지 않도록 우선순위를 조정하지만, 컨테이너 쪽 OOM 우선순위는 그렇게 조정되지 않기 때문에 개별 컨테이너가 희생되기 쉽습니다.
중요한 점은 OOMKilled = 무조건 애플리케이션 버그가 아니라는 것입니다. 컨테이너 메모리 제한이 너무 낮아도 OOMKilled가 날 수 있고, 반대로 제한이 아예 없어도 호스트 전체 메모리가 바닥나면서 컨테이너가 죽을 수 있습니다. Docker 공식 문서도 기본적으로 컨테이너는 자원 제한이 없고, 메모리 제한은 직접 설정해야 한다고 안내합니다.
exit code 137은 왜 자주 보일까
exit code 137은 SIGKILL(9)로 종료됐다는 뜻입니다. Docker 문서에도 docker ps -a --filter 'exited=137'로 이런 컨테이너를 찾을 수 있다고 나와 있습니다. 다만 여기서 주의할 점은, 137이 곧바로 OOMKilled만 의미하는 것은 아니라는 점입니다. 강제 종료가 나도 137이 될 수 있기 때문입니다. 그래서 실제로는 OOMKilled 여부와 커널 로그를 같이 확인해야 합니다.
Docker 컨테이너 갑자기 죽는 이유를 가장 빨리 찾는 확인 순서
1. 종료 코드와 상태부터 확인하기
가장 먼저 해야 할 일은 “왜 죽었는지 추측”이 아니라 “어떻게 종료됐는지 확인”입니다.
docker ps -a
docker ps -a --filter 'exited=137'
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.Error}}' <container_name>
docker inspect -f '{{.RestartCount}}' <container_name>
여기서 137이면 강제 종료 계열, 143이면 보통 SIGTERM을 받고 내려간 경우로 보는 편이 맞습니다. Docker 이벤트 예시에서도 signal=15 뒤에 exitCode=143, signal=9 뒤에 exitCode=137이 보입니다.
2. 죽기 직전 로그 확인하기
종료 원인의 절반은 로그에서 바로 보입니다.
docker logs --tail 100 <container_name>
docker logs -f <container_name>
Docker 문서 기준으로 docker logs는 해당 시점까지의 STDOUT과 STDERR 로그를 확인하는 기본 명령입니다. 그래서 앱 예외, 포트 충돌, DB 연결 실패, 환경 변수 누락 같은 원인은 여기서 먼저 드러나는 경우가 많습니다.
3. 이벤트 타임라인 보기
컨테이너가 언제 죽고 다시 떴는지, 누가 어떤 신호를 보냈는지 보려면 이벤트가 유용합니다.
docker events --since 30m --filter 'container=<container_name>'
Docker 이벤트에는 start, kill, die, stop 같은 흐름과 함께 signal=15, signal=9, exitCode=143, exitCode=137 같은 정보가 남습니다. 이걸 보면 “앱이 죽은 뒤 재시작된 것인지”, “정지 신호를 받고 정상 종료를 못 해서 강제 종료된 것인지”가 훨씬 선명해집니다.
4. 호스트 메모리와 커널 OOM 로그 확인하기
OOMKilled 의심이 들면 이 단계는 거의 필수입니다.
free -h
docker stats --no-stream
dmesg -T | egrep -i 'oom|out of memory|killed process'
journalctl -k | egrep -i 'oom|out of memory|killed process'
리눅스 커널은 OOM 상황에서 어떤 작업을 희생시켰는지 로그를 남깁니다. Docker 문서도 컨테이너 메모리 관리와 OOM 위험을 설명하면서, 호스트 전체가 메모리 부족에 빠질 수 있음을 강조합니다. 즉, Docker 컨테이너 갑자기 죽는 이유가 OOMKilled인지 확인할 때는 컨테이너 내부만 보면 안 되고, 반드시 호스트 로그까지 봐야 합니다.
왜 이런 문제가 발생하는가
1. 컨테이너 메모리 제한이 없거나 너무 낮다
Docker 컨테이너 갑자기 죽는 이유 중 가장 흔한 케이스입니다. 기본적으로 컨테이너는 자원 제한 없이 실행될 수 있고, 반대로 너무 낮은 메모리 상한을 걸어도 정상 동작에 필요한 메모리를 못 써서 OOMKilled가 납니다. Docker 문서는 -m/--memory로 하드 리밋을, 필요하면 --memory-swap 등으로 추가 제어를 하라고 안내합니다.
2. 앱 프로세스가 스스로 죽는다
Spring Boot, Node.js, Python, Nginx처럼 어떤 이미지든 결국 핵심은 PID 1 프로세스가 살아 있느냐입니다. 앱 시작 시 예외가 나거나, 환경 변수가 빠졌거나, DB 연결 실패로 부팅이 중단되면 컨테이너도 바로 종료됩니다. 컨테이너가 죽는 것은 Docker 자체의 이상이 아니라, 내부의 메인 프로세스가 끝났기 때문인 경우가 많습니다. 이럴 때는 종료 코드보다 로그가 더 중요합니다.
3. 의존 서비스는 아직 준비 안 됐는데 앱이 먼저 올라온다
Docker Compose는 의존 관계에 따라 시작 순서는 맞춰주지만, 기본적으로 “서비스가 준비 완료됐는지”까지는 기다리지 않습니다. 공식 문서에도 Compose는 컨테이너가 ready 상태가 될 때까지 기다리지 않고 running 상태까지만 본다고 나와 있습니다. 그래서 DB가 아직 pg_isready도 통과하지 못한 상태인데 앱이 먼저 떠서 연결 실패 후 종료하는 일이 자주 생깁니다.
4. 종료 신호를 앱이 제대로 받지 못한다
Docker는 컨테이너 정지 시 먼저 SIGTERM을 보내고, 유예 시간이 지나도 안 내려가면 SIGKILL을 보냅니다. 기본 유예 시간은 Linux 컨테이너 기준 10초입니다. 그런데 ENTRYPOINT나 CMD를 shell form으로 쓰면 실제 실행 파일이 셸의 자식 프로세스로 떠서 신호를 제대로 전달받지 못할 수 있습니다. Docker 문서도 이 때문에 JSON 배열 형태의 exec form 사용을 권장합니다.
5. 재시작 정책이 원인을 가려버린다
restart: always나 unless-stopped를 써 두면 컨테이너는 계속 다시 뜹니다. 운영에는 편하지만, 근본 원인을 가리기도 쉽습니다. Docker 문서 기준으로 on-failure는 비정상 종료일 때만 재시작하고, always와 unless-stopped는 동작 방식이 다릅니다. 즉, “살아는 있는데 자꾸 다시 떠요” 같은 상황도 사실은 계속 죽고 있는 상태일 수 있습니다.
6. 로그가 쌓여 디스크 압박이 생긴다
Docker는 기본 logging driver로 json-file을 사용하고, 기본 상태에서는 로그 로테이션이 수행되지 않습니다. Docker 문서도 출력이 많은 컨테이너는 이 로그가 쌓이면서 디스크 공간 고갈로 이어질 수 있다고 안내합니다. 이 경우 컨테이너를 Docker가 직접 “죽이는” 것과는 다르지만, 애플리케이션 쓰기 실패나 재배포 실패의 원인이 되기 쉽습니다.
상황별 해결 방법
1. OOMKilled일 때
먼저 메모리를 어디서 쓰는지 봐야 합니다.
docker stats --no-stream
free -h
dmesg -T | egrep -i 'oom|out of memory|killed process'
그다음 컨테이너에 메모리 제한을 명시적으로 둡니다. Docker 문서상 -m/--memory는 최소 6MB 이상이어야 하며, 필요하면 스왑 옵션도 함께 조정할 수 있습니다. 다만 --oom-kill-disable은 매우 조심해야 합니다. Docker 공식 문서도 이 옵션은 반드시 메모리 제한과 함께 써야 하며, 무턱대고 쓰면 호스트 전체가 위험해질 수 있다고 경고합니다.
예시: 실행 옵션
docker run -d \
--name my-app \
--memory="512m" \
--memory-swap="768m" \
--restart unless-stopped \
my-app:latest
예시: Compose
services:
app:
image: my-app:latest
mem_limit: 512m
restart: unless-stopped
운영용이라면 메모리 제한을 “감”으로 넣기보다, 며칠간 실제 사용량을 보고 잡는 편이 훨씬 안전합니다. Docker 컨테이너 갑자기 죽는 이유가 OOMKilled였다면, 이 단계가 가장 먼저 정리되어야 합니다.
2. 앱이 스스로 종료할 때
이 경우는 로그가 핵심입니다.
docker logs --tail 200 <container_name>
보통 아래 중 하나가 원인입니다.
- 환경 변수 누락
- 포트 충돌
- DB 접속 실패
- 시작 명령어 오타
- 마이그레이션 실패
- 인증서/파일 경로 누락
즉, 컨테이너 문제처럼 보여도 사실은 앱 부팅 실패입니다. 이런 경우 재시작 정책만 바꿔서는 해결되지 않습니다. 먼저 로그 한 줄을 정확히 읽는 게 우선입니다.
3. DB 준비 전 앱이 먼저 떠서 죽을 때
이건 Compose에서 정말 자주 나옵니다. 공식 문서대로라면 Compose는 기본적으로 ready 상태까지 기다리지 않기 때문에, DB 같은 의존 서비스가 준비될 때까지 healthcheck 기반으로 기다리게 만드는 편이 안전합니다.
예시
services:
app:
image: my-app:latest
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:18
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
retries: 5
start_period: 30s
timeout: 10s
이렇게 하면 “컨테이너는 떴는데 앱은 바로 죽는” 문제를 꽤 많이 줄일 수 있습니다. 특히 Docker 컨테이너 갑자기 죽는 이유가 배포 직후 집중된다면, 이 항목을 가장 먼저 의심해볼 만합니다.
4. 종료 신호 처리 문제일 때
앱이 SIGTERM을 받으면 정리 작업을 하고 정상 종료해야 하는데, 그게 안 되면 유예 시간 뒤 SIGKILL이 들어갑니다. Docker 문서 기준으로 기본 종료 흐름은 SIGTERM → 대기 → SIGKILL입니다.
특히 Dockerfile에서 아래처럼 shell form을 쓰면 신호 처리 문제가 생길 수 있습니다.
ENTRYPOINT java -jar app.jar
가능하면 아래처럼 exec form으로 바꾸는 편이 좋습니다.
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker 문서도 exec form이 메인 프로세스를 PID 1로 실행하므로 OS 신호를 올바르게 받을 수 있다고 설명합니다. 배포 때만 컨테이너가 이상하게 오래 걸리다 죽는다면 이 부분을 꼭 확인해야 합니다.
5. 로그 폭증과 디스크 압박일 때
로그가 많은 서비스라면 logging driver와 로테이션을 같이 봐야 합니다. Docker 문서에는 기본 json-file 드라이버는 기본 상태에서 로그 로테이션이 없고, local 드라이버는 로테이션을 기본으로 수행한다고 나와 있습니다.
daemon.json 예시
{
"log-driver": "local"
}
또는 json-file을 유지하더라도 최소한 제한은 두는 편이 낫습니다.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
운영 중인데 디스크가 자꾸 차고, 그때부터 앱이 비정상 종료되기 시작했다면 Docker 컨테이너 갑자기 죽는 이유가 메모리가 아니라 로그일 수도 있습니다.
운영용 기준으로 추천하는 점검 순서
1단계
종료 코드 확인
docker ps -a
docker ps -a --filter 'exited=137'
2단계
직전 로그 확인
docker logs --tail 200 <container_name>
3단계
이벤트 확인
docker events --since 30m --filter 'container=<container_name>'
4단계
OOM 여부 확인
free -h
docker stats --no-stream
dmesg -T | egrep -i 'oom|out of memory|killed process'
5단계
설정 수정
- OOMKilled면 메모리 제한 재설계
- DB 준비 문제면 depends_on + healthcheck
- 종료 신호 문제면 exec form과 stop 흐름 점검
- 로그 문제면 로테이션 설정
이 순서대로 보면 Docker 컨테이너 갑자기 죽는 이유를 훨씬 빠르게 좁힐 수 있습니다. 중요한 건 “재시작됐다”는 결과보다 왜 종료됐는지를 먼저 잡는 것입니다.
'IT > Docker' 카테고리의 다른 글
| Docker 로그 용량이 계속 쌓일 때, 디스크부터 정리하는 점검 순서 총정리 (0) | 2026.04.08 |
|---|---|
| Docker 메모리 제한 설정 방법 (0) | 2026.03.26 |
| Docker 로그관리 하는 방법 (0) | 2026.02.21 |
| 도커 볼륨 docker Volume (0) | 2026.02.10 |
| Docker Compose정리 (1) | 2026.02.09 |