Docker 메모리 제한 가이드: Compose로 OOM 장애 막는 법

  • Post last modified:2026년 08월 23일
  • Post category:기술

Docker 컨테이너의 메모리를 제한하지 않으면 한 서비스의 누수나 순간 부하가 호스트 전체의 OOM 장애로 번질 수 있다. 개인 VPS와 홈서버에서는 서비스별 hard limit를 먼저 두고, 호스트 여유 메모리를 남긴 뒤, OOMKilled와 재시작 횟수를 함께 관측하는 구성이 가장 현실적이다. restart만 설정하거나 swap만 늘리는 방식은 원인을 해결하지 못한다.

핵심 요약

  • Docker 컨테이너는 기본적으로 호스트가 허용하는 만큼 자원을 사용할 수 있으므로 운영 서버에서는 메모리 경계를 명시하는 편이 안전하다.
  • mem_limit는 hard limit, mem_reservation은 경합 때 작동하는 soft limit다. reservation은 초과 방지를 보장하지 않는다.
  • plain Docker Compose의 healthcheck는 상태를 표시하지만 unhealthy 컨테이너를 자동 재시작하지 않는다. 재시작은 프로세스 종료와 함께 설계해야 한다.
  • 2GB·4GB 소형 서버에서는 모든 컨테이너 hard limit 합계를 RAM과 같게 잡지 말고 OS·Docker·파일 캐시·배포 피크용 여유를 남긴다.

메모리 제한이 없으면 왜 호스트까지 흔들릴까

Docker 공식 문서에 따르면 컨테이너에는 기본 자원 제한이 없으며 호스트 커널 스케줄러가 허용하는 만큼 메모리와 CPU를 사용할 수 있다. Linux가 중요한 시스템 기능을 유지할 메모리를 확보하지 못하면 OOM 처리를 시작하고 프로세스를 종료한다. 컨테이너 프로세스가 먼저 종료될 가능성이 높지만 Docker 데몬이나 같은 호스트의 다른 서비스도 영향권에 들어간다.

문제는 평균 사용량이 아니라 피크의 합이다. 평소 250MB인 웹 앱이 배포 중 700MB를 쓰고, 같은 시각 데이터베이스 백업과 로그 압축이 겹치면 2GB VPS는 짧은 순간에도 한계에 닿는다. 따라서 “평소에는 괜찮다”보다 동시에 발생할 수 있는 최대 작업을 기준으로 경계를 잡아야 한다.

신호의미먼저 확인할 것
OOMKilled=true커널이 컨테이너 프로세스를 메모리 부족으로 종료했을 가능성이 크다.limit, 직전 RSS, 호스트 available 메모리
재시작 횟수 증가프로세스 종료 후 restart 정책이 반복 실행 중이다.종료 코드, 애플리케이션 로그, OOM 여부
swap 지속 사용RAM 압박을 디스크 I/O로 미루고 있다.응답 지연, major page fault, 스왑 설정
호스트 SSH 지연컨테이너 밖의 시스템 여유도 부족하다.OS·Docker·파일 캐시용 미할당 공간
Docker 메모리 제한 복구 정책 호스트 관측의 3단계 방어선
컨테이너 제한·복구 정책·호스트 관측의 3단계 방어선
출처: Docker 공식 문서 기반 구성

Docker Compose에서 hard limit와 soft reservation을 함께 둔다

아래 예시는 2GB VPS에서 웹 앱과 데이터베이스를 함께 운영할 때의 출발용 구성이다. 512M과 768M은 Docker가 제시한 보편 권장값이 아니라, 나머지 메모리를 호스트와 배포 피크에 남기기 위한 예시다. 실제 값은 docker stats와 서비스 부하 테스트로 조정해야 한다.

services:
  app:
    image: example/app:latest
    mem_limit: 512m
    mem_reservation: 256m
    pids_limit: 200
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s

  db:
    image: postgres:18
    mem_limit: 768m
    mem_reservation: 512m
    restart: unless-stopped
  • mem_limit: 컨테이너가 할당할 수 있는 메모리 상한이다. 상한을 너무 낮추면 정상적인 피크도 OOM으로 처리된다.
  • mem_reservation: 호스트에 경합이 생길 때 적용되는 soft limit다. hard limit보다 작게 두며 초과를 완전히 막는 장치로 보면 안 된다.
  • pids_limit: 프로세스·스레드 폭증이 호스트를 압박하는 범위를 줄인다. 메모리 제한과 함께 두면 장애 반경을 줄이기 쉽다.
  • restart: 종료된 프로세스를 다시 띄우는 정책이다. 메모리 누수의 원인을 고치거나 상태를 정상으로 만드는 기능은 아니다.

Compose의 deploy.resources.limits.memoryreservations.memory도 공식 Deploy Specification에 정의돼 있다. 같은 파일에서 서비스 수준의 mem_limit·mem_reservation을 함께 사용한다면 값이 서로 일치해야 한다. 배포 환경이 Docker Compose인지 Swarm인지도 구분해야 한다.

2GB·4GB VPS는 컨테이너 합계보다 여유 공간이 중요하다

소형 서버의 시작점은 컨테이너별 평균값을 더하는 방식이 아니라, hard limit 합계와 호스트 여유를 동시에 보는 방식이다. 아래 비율은 공식 최소 요구 사항이 아니라 개인 서비스의 보수적인 초기 운영 휴리스틱이다.

호스트 RAM컨테이너 hard limit 합계 시작점호스트·피크 여유적합한 상황
2GB약 1.2~1.5GB약 25~40%웹 앱+소형 DB, 경량 WordPress
4GB약 2.6~3.2GB약 20~35%여러 앱, 앱+DB+모니터링
8GB약 5.5~6.5GB약 20~30%빌드·크롤링·검색 작업이 섞인 서버

가장 위험한 구성은 모든 컨테이너 limit의 합계를 호스트 RAM과 같게 두는 것이다. Docker 데몬, 커널, SSH, 파일 캐시, 로그, 백업, 이미지 pull과 압축 해제도 메모리를 사용한다. 자세한 서버 사양 출발점은 개인 서비스 VPS 사양 선택법과 함께 보면 판단하기 쉽다.

swap은 완충 장치이지 메모리 증설이 아니다

memswap_limit는 메모리와 swap의 합계 한도를 조정한다. 공식 문서 기준으로 memory와 같은 값으로 설정하면 컨테이너의 swap 사용을 막고, memory만 설정한 채 memswap_limit를 비워 두면 호스트에 swap이 있을 때 memory 값만큼의 swap을 추가로 사용할 수 있다. 예를 들어 memory가 300M이면 합계 600M까지 가능할 수 있다.

swap은 OOM까지 시간을 벌 수 있지만 반복적인 디스크 교환은 지연을 키운다. 데이터베이스나 응답 시간에 민감한 API에서 swap 사용량이 계속 증가한다면 “안 죽었다”가 아니라 “느리게 장애가 진행 중이다”라고 해석하는 편이 안전하다. swap을 끄기 전에 실제 working set과 순간 피크를 확인해야 한다.

restart와 healthcheck를 과신하면 안 된다

Docker의 restart policy는 컨테이너가 종료됐을 때 동작한다. 공식 문서상 정책은 컨테이너가 최소 10초 동안 정상적으로 시작된 뒤 적용되며, 수동으로 중지한 컨테이너에는 데몬 재시작 또는 수동 재시작 전까지 정책이 무시된다. unless-stopped는 개인 서버의 일반적인 상시 서비스에 편리하지만 무한 재시작 루프를 정상화하지는 못한다.

또한 plain Docker Compose에서 healthcheck는 healthy·unhealthy 상태를 기록할 뿐 unhealthy만으로 컨테이너를 재시작하지 않는다. 애플리케이션이 복구 불가능한 상태에서 명확히 종료하도록 만들거나, 별도의 감시·오케스트레이션 정책을 설계해야 한다. AI 서비스의 추적·알림 설계는 AI 에이전트 관측 도구 비교의 기준도 참고할 수 있다.

OOM이 발생했을 때 10분 안에 확인할 순서

  1. docker inspect <container> --format {{.State.OOMKilled}}로 OOM 종료 여부를 확인한다.
  2. docker stats --no-stream으로 현재 사용량과 설정된 limit를 함께 기록한다.
  3. docker inspect에서 종료 코드, 재시작 횟수, 메모리 설정을 확인한다.
  4. 애플리케이션 로그와 커널 로그에서 종료 직전의 배포·백업·요청 급증 시점을 맞춘다.
  5. limit만 즉시 올리지 말고 누수, 캐시 상한, 작업 동시성, DB 버퍼 설정을 먼저 분리한다.
  6. 재현 부하에서 working set과 피크를 측정한 뒤 reservation과 limit를 단계적으로 조정한다.
docker inspect app --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.RestartCount}}'
docker stats --no-stream
docker inspect app --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}}'

로컬 LLM이나 임베딩 서비스는 모델 파일 외에도 KV 캐시와 런타임 버퍼가 커질 수 있다. 이런 워크로드라면 로컬 LLM 메모리 계산법으로 컨테이너 limit 이전에 전체 메모리 예산부터 계산하는 편이 낫다.

배포 전 체크리스트

  • 각 서비스에 hard limit가 있으며 정상 피크보다 낮게 잡히지 않았는가
  • reservation이 hard limit보다 작고 실제 steady-state 사용량과 맞는가
  • 모든 hard limit 합계 외에 OS·Docker·캐시·배포용 여유가 남는가
  • restart 횟수와 OOMKilled를 모니터링하거나 정기 확인하는가
  • healthcheck 실패가 실제 복구 동작으로 이어지는지 별도로 검증했는가
  • swap 사용량 증가와 지연을 함께 보고 있는가
  • 백업·빌드·로그 압축이 같은 시간대에 겹치지 않는가

FAQ

mem_limit만 설정하면 OOM 장애가 사라질까?

아니다. 호스트 전체 장애의 반경은 줄일 수 있지만 limit가 너무 낮으면 컨테이너 내부 OOM이 더 자주 발생한다. 정상 피크 측정, 호스트 여유, 재시작 원인 기록을 함께 운영해야 한다.

mem_reservation과 mem_limit의 차이는 무엇인가?

reservation은 메모리 경합 때 쓰는 soft limit이고 limit는 할당 상한인 hard limit다. reservation은 컨테이너가 그 값을 넘지 않는다고 보장하지 않는다.

healthcheck가 unhealthy면 Docker가 자동으로 재시작할까?

plain Docker Compose에서는 그렇지 않다. healthcheck는 상태를 표시하며 restart policy는 기본적으로 프로세스 종료에 반응한다. 종료 전략이나 별도 감시 계층이 필요하다.

2GB VPS에서 Docker를 몇 개까지 실행할 수 있을까?

개수만으로 정할 수 없다. 경량 프록시 여러 개보다 DB 하나가 더 많은 메모리를 쓸 수 있다. 각 서비스 working set, 동시 피크, hard limit 합계와 호스트 여유로 판단해야 한다.

참고 자료

답글 남기기