Docker 재시작 후 PostgreSQL 데이터가 사라지는 이유와 영구 저장 방법

Docker 컨테이너를 재생성했더니 PostgreSQL 데이터가 사라졌던 원인을 정리합니다. 원인은 PostgreSQL 자체가 아니라 볼륨 설정이었습니다.

Docker로 PostgreSQL을 운영하다가 한 번쯤은 이런 상황을 겪게 됩니다.

컨테이너를 재생성했더니 데이터베이스가 텅 비어 있었습니다. 처음에는 PostgreSQL 자체 문제인 줄 알았습니다. 로그를 확인하고 설정을 점검했지만 이상이 없었습니다. 원인은 Docker 볼륨 설정이었습니다.

UGREEN DXP4800PLUS NAS에서 쇼츠 자동화 시스템과 트레이딩 봇을 운영하면서 이 문제를 직접 겪었습니다. 이 글에서는 컨테이너를 재생성한 뒤 이전 PostgreSQL 데이터가 새 컨테이너에서 보이지 않는 원인과 NAS 환경에서 저장소를 명시적으로 유지하는 방법을 정리합니다.


왜 데이터가 사라지는가

Docker 컨테이너는 애플리케이션 실행 환경만 보관합니다.

PostgreSQL 데이터 디렉터리를 named volume이나 bind mount처럼 다시 연결할 수 있는 저장소에 두어야 새 컨테이너에서도 같은 데이터를 계속 사용할 수 있습니다. 익명 volume도 컨테이너 삭제 뒤 남을 수 있지만 새 컨테이너에 자동으로 다시 연결되지는 않습니다. Docker 공식 volume 문서도 익명 volume은 지속될 수 있지만 이후 컨테이너에 자동 재사용되지 않는다고 설명합니다.

가장 흔한 실수는 데이터가 어느 volume에 저장되는지 확인하지 않은 채 컨테이너를 재생성하는 것입니다.

yaml
# 명시적인 named/bind mount가 없는 구성
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword

이 구성은 PostgreSQL 이미지가 익명 volume을 만들 수 있어 컨테이너 삭제 직후 데이터 자체가 곧바로 지워진다고 단정할 수 없습니다. 다만 익명 volume은 다음 docker compose up에서 자동 재사용되지 않을 수 있어 새 컨테이너가 새 volume을 연결하면 이전 데이터가 사라진 것처럼 보입니다. 운영 환경에서는 named volume 또는 bind mount를 명시해 재연결할 저장소를 고정합니다.

bash
# down은 컨테이너를 제거하지만 named volume은 -v 없이는 제거하지 않음
docker compose down

# 이건 괜찮음 (컨테이너 중지만, 삭제 안 함)
docker compose stop

Docker Volume 개념

데이터를 영구 보존하려면 볼륨(Volume)을 사용해야 합니다.

Docker Volume 방식은 두 가지입니다.

Named Volume (도커 관리 볼륨)

services:
  postgres:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Docker가 볼륨을 관리합니다. 경로는 /var/lib/docker/volumes/ 아래에 저장됩니다.

Bind Mount (직접 경로 지정)

services:
  postgres:
    image: postgres:15-alpine
    volumes:
      - /volume2/docker/postgres/data:/var/lib/postgresql/data

호스트의 특정 경로에 직접 저장합니다. NAS 환경에서는 이 방식이 더 유리합니다.


NAS 환경에서 올바른 구성

UGREEN DXP4800PLUS NAS에서는 Bind Mount 방식을 권장합니다.

이유:

  • NAS 파일 관리자에서 직접 데이터 확인 가능
  • 백업이 쉬움
  • 데이터 위치가 명확함
yaml
services:
  postgres:
    image: postgres:15-alpine
    container_name: shorts-postgres
    environment:
      POSTGRES_DB: shorts
      POSTGRES_USER: shorts
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      TZ: Asia/Seoul
    volumes:
      - /volume2/docker/shorts-trend/postgres/data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U shorts"]
      interval: 5s
      timeout: 5s
      retries: 5

/volume2/docker/shorts-trend/postgres/data 경로에 PostgreSQL 데이터가 저장됩니다. 컨테이너를 삭제해도 이 폴더는 남아있습니다.


볼륨 확인 방법

bash
# 현재 Docker 볼륨 목록
docker volume ls

# 특정 볼륨 상세 정보
docker volume inspect postgres_data

# Bind Mount 확인 (실제 경로에 파일 있는지)
ls /volume2/docker/shorts-trend/postgres/data

정상적으로 설정됐다면 ls 결과에 PG_VERSION, base, global 등의 파일과 폴더가 보입니다.

Docker 볼륨과 PostgreSQL 데이터 경로를 확인하는 터미널 화면

[ls /volume2/docker/ 결과]

UGREEN DXP4800PLUS NAS /volume2/docker 폴더 구조
서비스별로 분리된 데이터 저장 경로


실제로 겪은 사례

트레이딩 봇 시스템을 개발하면서 이 문제를 겪었습니다.

포지션 데이터, 주문 내역, 체결 데이터를 PostgreSQL에 저장하고 있었는데 컨테이너를 재생성할 때마다 데이터가 초기화됐습니다.

쇼츠 자동화 시스템에서도 같은 실수를 했습니다.

수집한 영상 데이터 수백 개가 사라진 후에야 볼륨 설정을 제대로 잡았습니다. 다행히 테스트 환경이었지만 실서비스였다면 다시 수집하는 데 수 시간 이상이 걸릴 상황이었습니다.

현재는 /volume2/docker 하위에 서비스별로 폴더를 분리해서 데이터를 영구 보존하고 있습니다.


주의사항

docker compose down vs stop

bash
# 컨테이너·기본 네트워크 삭제 (named/anonymous volume은 기본적으로 남음)
docker compose down

# 컨테이너 중지만 (데이터 유지)
docker compose stop

# 볼륨까지 삭제 (절대 주의)
docker compose down -v

docker compose down -v 는 Named Volume까지 삭제합니다. 절대 운영 환경에서 실수로 실행하지 않도록 주의해야 합니다.

폴더 권한 문제

NAS에서 Bind Mount 사용 시 폴더 권한 오류가 발생할 수 있습니다.

# 사용하는 이미지의 postgres UID/GID를 직접 읽어 적용
IDS=$(docker run --rm --entrypoint sh postgres:15-alpine -c 'id -u postgres; id -g postgres')
POSTGRES_UID=$(printf '%s\n' "$IDS" | sed -n '1p')
POSTGRES_GID=$(printf '%s\n' "$IDS" | sed -n '2p')
sudo chown -R "${POSTGRES_UID}:${POSTGRES_GID}" /volume2/docker/postgres/data

UID/GID를 999:999처럼 고정값으로 가정하면 이미지 태그에 따라 틀릴 수 있습니다. 이 NAS에서 현재 postgres:15-alpine을 직접 확인했을 때는 uid=70(postgres) gid=70(postgres)이었지만, 실제 배포에 사용하는 이미지에서 id postgres 결과를 확인한 뒤 적용해야 합니다.


마무리

Docker PostgreSQL의 데이터 지속성은 컨테이너 자체가 아니라 어떤 저장소를 다시 연결하느냐로 결정됩니다. 핵심은 두 가지입니다.

첫째, docker compose down은 컨테이너를 제거하지만 named volume은 -v를 지정하지 않는 한 제거하지 않습니다. 익명 volume은 남아도 새 컨테이너에 자동 재연결되지 않을 수 있으므로 운영 데이터에는 명시적인 named volume 또는 bind mount를 사용합니다.

둘째, NAS 환경에서는 Bind Mount 방식으로 /volume2/docker/ 하위에 직접 경로를 지정하는 것이 관리하기 편합니다.

실서비스 전에 반드시 docker compose down && docker compose up으로 데이터 유지 여부를 테스트해보는 것을 권장합니다.


관련 글