SSD Nodes Learn 8GB RAM — 연 $66
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-01

Docker Compose bind mount와 named volume 차이

Docker Compose에서 config와 database data에 bind mount와 named volume 중 무엇을 쓸지 비교합니다. 권한 문제, 읽기 전용 설정, 볼륨 이름 규칙, inspect와 백업 및 마이그레이션 방법을 설명합니다.

Bind mount 또는 named volume: 간단한 답

Docker Compose volume은 2가지 유형으로 제공되며, 선택 기준은 파일을 누가 소유하는지입니다. 직접 작성하고 읽는 파일에는 bind mount를 사용합니다. 예를 들어 config, template, static site가 해당합니다. 애플리케이션이 소유하는 데이터에는 named volume을 사용합니다. 예를 들어 database file, search index, 업로드된 media가 해당합니다. bind mount는 editor에서 열 수 있는 host의 path를 가리킵니다. named volume은 Docker가 생성하고 관리하는 storage이며, Docker를 통해 접근합니다.

두 유형 모두 service 내부의 동일한 volumes: key 아래에 표시되므로 혼동하기 쉽습니다. 차이는 colon의 왼쪽 부분에 있습니다. . 또는 /로 시작하는 왼쪽 부분은 host path이므로 bind mount입니다. 그 외의 값은 name이므로 named volume이며, 해당 name은 top-level volumes: block에도 선언해야 합니다.

compose 파일의 두 가지 구문

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data는 이름이 지정된 볼륨입니다. ./nginx.conf:/etc/nginx/nginx.conf는 bind mount이며, :ro은 해당 마운트를 읽기 전용으로 설정합니다. 컨테이너가 다시 작성해서는 안 되는 구성에는 읽기 전용이 올바른 기본값입니다. 최상위 volumes: 항목을 누락하면 Compose가 service "db" refers to undefined volume pgdata 오류와 함께 중지됩니다.

스택을 시작하고 Docker가 생성한 항목을 나열합니다.

docker compose up -d
docker volume ls

볼륨 이름은 pgdata가 아닙니다. 기본적으로 compose 파일이 있는 디렉터리 이름을 프로젝트 이름으로 사용하므로 볼륨 이름은 <project>_pgdata입니다. 디렉터리 이름이 myapp이면 myapp_pgdata가 됩니다. 디렉터리 이름을 변경하면 새로운 빈 볼륨이 생성되고 애플리케이션에서 데이터가 사라진 것처럼 보이므로 이 점이 중요합니다. 데이터가 사라진 것은 아닙니다. 기존 볼륨은 docker volume ls으로 계속 나열됩니다. 디렉터리가 이동할 수 있다면 compose 파일에서 name:로 이름을 고정하거나 COMPOSE_PROJECT_NAME를 설정합니다. 이러한 설정은 다른 Compose 환경 파일 및 시크릿과 함께 관리합니다.

권한 오류가 bind mount에서만 발생하는 이유

이것이 가장 큰 실무상 차이입니다. 그 원인은 한 가지 규칙에 있습니다. 처음 사용할 때 비어 있는 named volume은 image에서 초기 내용이 복사되지만, bind mount에는 이러한 동작이 없습니다.

Docker가 image에 이미 내용이 있는 directory 위에 비어 있는 named volume을 mount하면, image에서 해당 내용을 volume으로 복사합니다. 이때 image가 설정한 소유권과 mode도 함께 복사됩니다. 공식 Postgres image는 /var/lib/postgresql/data를 자체 postgres user가 소유하도록 제공하므로, volume도 동일한 numeric id가 소유하게 되고 database가 시작됩니다.

bind mount는 반대로 동작합니다. host에 있는 것이 소유권을 포함해 container에 그대로 표시되며, 해당 경로의 image 내용은 가려집니다. host directory가 없으면 Docker daemon이 이를 생성합니다. daemon은 root로 실행되므로 해당 directory의 소유자는 root:root이 됩니다. 그러면 non-root user로 실행되는 container process가 여기에 쓸 수 없습니다.

PermissionError: [Errno 13] Permission denied: '/data/app.db'

해결 방법은 숫자를 일치시키는 것입니다. bind mount에서 소유권은 이름이 아니라 numeric user id로 비교됩니다. container에는 자체 /etc/passwd가 있기 때문입니다. container 내부의 app라는 user 이름은 host에서 의미가 없습니다. Uid 1000은 양쪽 모두에서 uid 1000을 의미합니다.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id은 container process가 실제로 실행되는 uid를 출력합니다. host directory를 해당 숫자에 맞추거나, service에서 user: "1000:1000"를 사용해 container를 자신의 숫자로 고정합니다. 직접 작성한 application에는 user:를 고정하는 방식이 더 깔끔합니다. 직접 작성하지 않은 image에는 host directory의 소유자를 변경하는 방식이 더 안전합니다. 일부 image는 entrypoint를 root로 시작한 뒤 권한을 낮추고, 내부에 특정 소유권이 설정되어 있기를 기대하기 때문입니다.

추가로 알아둘 함정이 2가지 있습니다. Fedora, RHEL 및 SELinux (security-enhanced Linux)가 enforcing 상태인 기타 system에서는 relabeling하기 전까지 bind mount가 거부됩니다. 따라서 여러 container가 공유하는 path에는 :z를 추가하고, 하나의 container만 사용해야 하는 path에는 :Z를 추가하며, 표기는 - ./data:/data:Z와 같이 작성합니다. 또한 directory가 아니라 단일 file을 bind mount하면 editor가 file에 직접 쓰지 않고 교체할 때 문제가 발생합니다. mount가 원래 inode를 따르기 때문입니다. container는 재시작할 때까지 이전 내용을 계속 봅니다. file을 자주 편집한다면 parent directory를 mount하십시오.

성능: 차이가 실제로 발생하는 경우

Linux 서버에서는 두 방식 모두 동일한 커널 경로를 사용합니다. 따라서 처리량 차이가 작으므로 이를 기준으로 선택해서는 안 됩니다. 기본 local 드라이버를 사용하는 named volume은 /var/lib/docker/volumes/ 아래의 Docker 나머지 데이터와 동일한 파일 시스템에 저장됩니다. bind mount는 지정한 위치에 저장됩니다.

Docker Desktop for macOS 및 Windows에서는 차이가 발생합니다. 이 환경에서는 컨테이너가 가상 머신 내부에서 실행됩니다. 이때 bind mount는 파일 공유 계층을 통해 호스트 파일 시스템에서 해당 가상 머신으로 연결됩니다. Node.js 종속성 트리나 PHP framework 캐시처럼 작은 파일 작업이 많은 워크로드는 눈에 띄게 느려집니다. named volume은 가상 머신 내부에 유지되므로 이러한 비용이 발생하지 않습니다. 따라서 많은 개발용 compose 파일은 소스 디렉터리를 bind-mount하고 node_modules에는 named volume을 선언합니다.

실제로 다른 또 한 가지는 데이터가 저장되는 위치입니다. /mnt/backup에 대한 bind mount는 해당 디스크에 데이터를 저장합니다. named volume은 /var/lib/docker을 포함하는 파일 시스템에 저장되며, VPS에서는 대개 root 디스크입니다. named volume에서 커지는 데이터베이스는 시스템 로그가 저장된 동일한 디스크를 가득 채웁니다. 문제가 발생하기 전에 확인합니다.

docker system df -v
df -h /var/lib/docker

docker system df -v는 모든 volume과 각 volume의 크기를 나열하고, 더 이상 어떤 컨테이너도 참조하지 않는 volume을 표시합니다.

이름이 지정된 볼륨 검사

이름이 지정된 볼륨은 블랙박스가 아닙니다. Docker에 볼륨의 위치를 확인하도록 요청합니다.

docker volume inspect myapp_pgdata

Mountpoint 필드에는 실제 호스트 경로가 표시되며, 일반적으로 /var/lib/docker/volumes/myapp_pgdata/_data입니다. sudo ls를 사용하여 이 값을 읽을 수 있으며, 빠르게 확인할 때 유용합니다. 이 경로를 파일을 편집하는 위치로 사용해서는 안 됩니다. root로 이 경로에 쓰면 앞에서 설명한 소유권 문제가 다시 발생합니다. 또한 이 경로는 local 드라이버의 세부 정보이며, 다른 볼륨 드라이버에는 적용되지 않습니다.

내부를 확인하는 안전한 방법은 볼륨을 마운트하는 임시 컨테이너를 사용하는 것입니다.

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

이 방법은 모든 드라이버에서 작동합니다. 실제 컨테이너와 동일한 권한을 확인할 수 있습니다. 또한 --rm 때문에 컨테이너가 종료된 후 아무것도 남지 않습니다.

각 종류의 백업

bind mount는 일반 디렉터리이므로 파일 수준 백업 도구가 이미 처리할 수 있습니다. 백업 대상에 호스트 경로를 지정하면 됩니다. named volume은 도구가 volume 내부에 접근해야 하므로 한 단계가 더 필요합니다. volume과 호스트 디렉터리를 같은 임시 컨테이너에 mount한 다음 archive를 작성합니다.

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

새 volume에 반대 방향으로 복원합니다.

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

컨테이너 내부에서 root로 실행될 때 tar은 숫자 형식의 소유권을 유지합니다. 따라서 복원된 volume을 애플리케이션이 계속 사용할 수 있습니다.

두 종류 모두에 적용되는 주의 사항이 있습니다. 데이터베이스가 실행 중일 때 데이터베이스 파일을 복사하면 변경 중인 대상의 archive가 생성됩니다. 이 archive를 복원하면 데이터베이스가 손상된 상태가 될 수 있습니다. 먼저 서비스를 중지하거나 docker compose exec -T db pg_dump -U postgres appdb > appdb.sql처럼 데이터베이스 자체 도구를 사용해 dump를 생성합니다. 그러면 일반 파일이 생성되며, 이 파일을 compose 파일과 함께 일반 암호화된 restic 백업 절차에 포함할 수 있습니다.

bind mount를 named volume으로 마이그레이션

이동은 이름 변경이 아니라 복사이며, 약 1분 정도 걸립니다.

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a은 소유권, 모드 및 타임스탬프를 유지합니다. 따라서 이전 디렉터리를 읽을 수 있었던 컨테이너 사용자는 새 volume도 계속 읽을 수 있습니다. 그런 다음 서비스를 pgdata:/var/lib/postgresql/data을 사용하도록 변경하고, 최상위 volumes: 블록에 pgdata를 추가한 후, docker compose up -d를 실행합니다. 이전 디렉터리를 삭제하기 전에 애플리케이션 로그를 확인합니다. 반대 방향으로 이동하려면 동일한 명령에서 /from/to의 위치를 바꾸면 됩니다.

테스트할 때 한 가지를 기억해야 합니다. docker compose down는 named volume을 그대로 두지만, docker compose down -v는 프로젝트에서 선언한 모든 named volume을 삭제하며 실행을 취소할 수 없습니다. bind mount는 두 명령 모두에서 유지됩니다. Docker가 해당 디렉터리를 소유하지 않기 때문입니다. lifecycle 명령이 아직 익숙하지 않다면 VPS용 Docker Compose 기본 가이드에서 명령 사용 방법을 단계별로 설명합니다.

서비스별 선택

파일을 누가 작성하는지 확인합니다. 텍스트 편집기에서 수정하고 git에 커밋하는 구성은 bind mount에 속하며, :ro에 마운트합니다. 구성 내용을 확인하고 버전을 관리해야 하기 때문입니다. 직접 열어 보지 않는 애플리케이션 상태 데이터는 named volume에 속합니다. Docker가 권한을 올바르게 초기화하며, 데이터가 호스트 경로에 종속되지 않기 때문입니다.

미디어는 두 가지 방식이 섞이는 경우입니다. 사진 라이브러리는 애플리케이션이 작성하지만 사용자가 관리하기도 하며, 특정 디스크에 저장해야 할 만큼 큰 경우가 많습니다. 해당 디스크의 경로에 bind mount하고, 소유권을 한 번 명시적으로 설정합니다. 대부분의 self-hosted 스택은 이 패턴을 사용합니다. 데이터베이스와 캐시에는 named volume을 사용하고, 구성과 관리할 대용량 디렉터리에는 bind mount를 사용합니다.

FAQ

bind mount와 named volume의 차이는 무엇입니까?

bind mount는 호스트의 경로를 컨테이너에 매핑하므로 양쪽에서 같은 디렉터리를 확인할 수 있으며 일반 도구로 해당 디렉터리를 편집할 수 있습니다. named volume은 Docker가 생성하고 관리하는 스토리지입니다. 이름으로 참조하며 최상위 volumes: 블록에서 선언합니다. 실질적인 구분 기준은 관리 주체입니다. 직접 관리하는 구성에는 bind mount를 사용하고, 애플리케이션이 관리하는 데이터에는 named volume을 사용합니다.

named volume에서는 발생하지 않지만 bind mount에서 "permission denied"가 발생하는 이유는 무엇입니까?

비어 있는 named volume은 image에서 초기 파일을 복사하므로 image가 설정한 소유권을 상속합니다. 따라서 컨테이너 사용자가 해당 volume에 쓸 수 있습니다. bind mount는 호스트 디렉터리의 현재 상태를 그대로 표시합니다. Docker가 해당 디렉터리를 생성해야 했다면 디렉터리의 소유자는 root가 됩니다. docker compose exec <service> id을 실행하여 컨테이너가 사용하는 숫자 id를 확인한 다음, 호스트 디렉터리에 sudo chown -R <uid>:<gid>를 실행하거나 서비스에 user: "1000:1000"을 설정합니다.

Docker는 named volume을 디스크의 어디에 저장합니까?

기본 local driver를 사용하면 named volume은 /var/lib/docker/volumes/<volume>/_data 아래에 저장됩니다. docker volume inspect <volume>은 정확한 Mountpoint을 출력합니다. 확인이 필요하면 이 값을 읽어도 되지만, 해당 경로에는 컨테이너를 통해서만 쓰십시오. 호스트에서 root로 편집하면 소유권이 변경되어 컨테이너가 예상하지 못하는 상태가 될 수 있습니다.

named volume을 어떻게 백업합니까?

volume과 호스트 디렉터리를 모두 mount한 일시적인 컨테이너를 실행한 다음 docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .을 사용하여 한쪽에서 다른 쪽으로 archive를 생성합니다. 데이터베이스의 경우 실행 중인 파일을 복사하지 말고 데이터베이스 자체 도구를 사용하여 dump하십시오. 쓰기가 진행 중일 때 복사한 파일은 복원 후 손상된 상태가 될 수 있기 때문입니다.

docker compose down은 volume을 삭제합니까?

docker compose down은 컨테이너와 network를 제거하고 named volume은 유지합니다. docker compose down -v은 프로젝트에서 선언한 모든 named volume도 삭제하며, 삭제된 volume은 영구적으로 사라집니다. 두 명령 모두 bind mount는 삭제하지 않습니다. 해당 디렉터리는 Docker가 아니라 호스트에 속하기 때문입니다.