Docker (8) - 볼륨 마운트와 바인드 마운트
1. 개요
컨테이너 내부에 저장된 데이터는 컨테이너의 생명주기를 그대로 따라간다. 즉 컨테이너가 삭제되면 그 안에 쌓아둔 데이터도 함께 사라진다. DB 데이터처럼 컨테이너가 없어져도 유지되어야 하는 데이터가 있다면, 별도의 저장 매커니즘으로 데이터의 영속성을 확보해야 하는데 이때 쓰는 것이 볼륨이다.
Docker 볼륨은 스토리지의 한 영역을 분할한 것으로, 컨테이너에서 만들어서 재사용할 수 있고 호스트 운영체제에서 직접 접근도 가능하다. 이번 글에서는 볼륨을 마운트하는 두 가지 방식과 각각의 사용법을 정리한다.
2. 마운트 방식 두 가지
| 방식 | 설명 |
|---|---|
| 볼륨 마운트 | Docker Engine이 관리하는 영역 안에 볼륨을 만들고 마운트해서 사용 |
| 바인드 마운트 | 호스트 컴퓨터의 특정 디렉토리를 컨테이너 경로에 그대로 연결해서 사용 |
볼륨 마운트는 Docker가 스토리지 영역을 직접 관리해주는 반면, 바인드 마운트는 호스트의 기존 파일/디렉토리를 그대로 마운트하기 때문에 일반적인 파일처럼 다루고 백업할 수 있다는 차이가 있다.
3. 볼륨 마운트 사용하기
1
2
3
4
5
6
7
8
# 생성
docker volume create 이름
# 삭제
docker volume rm 이름
# 컨테이너에 연결
docker run -v 볼륨이름:컨테이너의_마운트경로
실습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 볼륨 생성
docker volume create my-app-vol-1
# 볼륨 목록 확인
docker volume ls
# ubuntu 컨테이너에 볼륨 연결
docker run -d -v my-app-vol-1:/var/log --name=vol-test ubuntu
# 볼륨 상세 정보 확인
docker volume inspect my-app-vol-1
# 볼륨 삭제
docker volume rm my-app-vol-1
볼륨은 사용 중인 컨테이너가 남아있으면 삭제되지 않는다. 볼륨을 지우려면 그 볼륨을 물고 있는 컨테이너부터 먼저 삭제해야 한다.
4. 바인드 마운트 사용하기
바인드 마운트는 볼륨 마운트보다 사용 범위가 제한적이지만, 호스트의 특정 경로를 그대로 컨테이너에 연결한다는 점에서 더 직관적으로 쓸 수 있다.
- 사용자가 지정한 호스트 경로가 존재하지 않으면 자동으로 생성되며, 이때 생성된 디렉토리는 루트 사용자 소유가 된다.
- 컨테이너를 실행할 때 지정해서 사용하며, 컨테이너를 삭제해도 마운트만 해제될 뿐 호스트 쪽 디렉토리와 파일은 그대로 남는다.
1
docker run -d -v 스토리지_실제경로:컨테이너_마운트경로 ubuntu
실습: httpd 컨테이너와 호스트 디렉토리 연결
1
2
3
4
5
# 마운트할 디렉토리 생성
mkdir apache
# htdocs 디렉토리와 연결
docker run --name apache -d -p 8000:80 -v $(pwd)/apache:/usr/local/apache2/htdocs httpd
이렇게 연결해두면 호스트의 apache 디렉토리에 파일을 놓기만 해도 컨테이너 안의 htdocs에 바로 반영되기 때문에, 컨테이너에 매번 docker cp로 파일을 복사하지 않아도 된다.
5. tmpfs 마운트
볼륨 마운트와 바인드 마운트가 데이터를 디스크에 남기는 방식이라면, tmpfs 마운트는 반대로 데이터를 디스크가 아닌 호스트의 메모리에만 잠깐 올려두는 방식이다. 컨테이너를 실행할 때 지정하고, 컨테이너가 사라지면 마운트도 함께 자동으로 해제된다. 비밀번호나 세션 값처럼 디스크에 흔적을 남기고 싶지 않은 민감한 데이터, 혹은 굳이 영속시킬 필요 없는 임시 캐시성 데이터를 다룰 때 적합하다.
1
docker run --tmpfs 컨테이너경로 이미지이름
실습: httpd 컨테이너의 htdocs를 tmpfs로 마운트
1
docker run --name webserver -dit --tmpfs /var/www/html httpd:2
이렇게 띄운 컨테이너는 /var/www/html에 파일을 아무리 써도 컨테이너를 지우는 순간 전부 사라진다. 데이터를 남기고 싶지 않을 때만 골라 쓰는 옵션이라고 생각하면 된다.
6. 실전 예제: MySQL 데이터 영속화
볼륨 마운트를 실제로 써먹는 대표적인 사례가 데이터베이스다. MySQL 컨테이너를 볼륨과 함께 띄워서, 컨테이너를 지웠다가 다시 만들어도 데이터가 그대로 남아있는지 확인해본다.
| 항목 | 내용 |
|---|---|
| 이미지 | mysql |
| 컨테이너 포트 | 3306 |
| 데이터 디렉토리 | /var/lib/mysql |
| 환경 변수 | MYSQL_ROOT_PASSWORD(루트 비밀번호), MYSQL_DATABASE(초기 생성할 데이터베이스) |
컨테이너 생성
1
2
3
4
5
docker run --name mysql-volume -dit \
-e MYSQL_ROOT_PASSWORD=1234 \
-e MYSQL_DATABASE=itstudy \
-v mysql-data-vol:/var/lib/mysql \
mysql
Apple Silicon Mac처럼 이미지가 바로 실행되지 않는 환경이라면 --platform linux/amd64 옵션을 추가하면 된다.
컨테이너와 볼륨 상태 확인
1
2
3
4
5
docker ps -a # 컨테이너 생성 여부
docker ps # 컨테이너 실행 여부
docker volume ls # 볼륨 목록
docker volume inspect mysql-data-vol # 볼륨 상세 정보
docker inspect mysql-volume --format '' | jq # 마운트 정보
컨테이너에 접속해서 샘플 데이터 생성
1
2
docker exec -it mysql-volume /bin/bash
mysql -uroot -p
1
2
3
4
use itstudy;
create table mytable(num int, name char(30));
insert into mytable values(1, "adam");
select * from mytable;
여기까지 확인했으면 컨테이너를 지워보자.
1
2
docker stop mysql-volume
docker rm mysql-volume
같은 볼륨으로 컨테이너를 다시 생성
1
2
3
4
5
docker run --name mysql-volume -dit \
-e MYSQL_ROOT_PASSWORD=1234 \
-e MYSQL_DATABASE=itstudy \
-v mysql-data-vol:/var/lib/mysql \
mysql
컨테이너 이름이나 다른 옵션이 달라져도 상관없다. 핵심은 -v mysql-data-vol:/var/lib/mysql로 지정한 볼륨이 같다는 것이다. 새로 만든 컨테이너에 다시 접속해서 조회해보면 조금 전에 넣은 데이터가 그대로 남아있는 것을 확인할 수 있다.
1
2
docker exec -it mysql-volume /bin/bash
mysql -uroot -p
1
2
use itstudy;
select * from mytable;
한 가지 주의할 점은, 볼륨 안에 이미 초기화된 데이터가 있는 상태에서 컨테이너를 새로 띄우면 MYSQL_ROOT_PASSWORD, MYSQL_DATABASE 환경 변수는 무시된다는 것이다. 두 값 모두 볼륨이 처음 초기화될 때 딱 한 번만 적용되고, 이후에는 그 볼륨을 최초로 초기화했을 때의 값이 계속 유지된다.
7. 실전 예제: nginx 로그를 호스트에 쌓기
이번에는 바인드 마운트로 nginx의 로그를 호스트 디렉토리에 그대로 쌓아본다.
| 항목 | 내용 |
|---|---|
| 이미지 | nginx |
| 공개 포트 | 80 |
| 로그 디렉토리 | /var/log/nginx |
먼저 호스트에 로그를 담을 디렉토리를 하나 만들고, 그 경로를 컨테이너의 로그 디렉토리에 그대로 연결한다.
1
2
3
4
5
6
7
8
mkdir -p ~/logs
docker run \
--name nginx-volume \
-dit \
-v ~/logs:/var/log/nginx \
-p 8000:80 \
nginx
localhost:8000으로 몇 번 접속한 뒤 호스트의 ~/logs 디렉토리를 열어보면, 컨테이너 안이 아니라 호스트 쪽에 접근 로그 파일이 그대로 쌓여 있는 것을 확인할 수 있다. 컨테이너를 지워도 로그 파일은 호스트에 남기 때문에, 로그 수집이나 분석 도구를 컨테이너 밖에서 붙이기에 유리하다.
8. 볼륨으로 컨테이너끼리 데이터 공유하기
볼륨은 데이터를 영속시키는 용도 외에, 여러 컨테이너가 같은 볼륨을 공유해서 서로 데이터를 주고받는 용도로도 쓸 수 있다. 이때 쓰는 옵션이 --volumes-from이다.
먼저 볼륨만 갖고 있는 컨테이너를 하나 만든다. 실행할 필요는 없고, /data-volume 경로에 볼륨만 갖고 있으면 된다.
1
docker create -v /data-volume --name=datavol ubuntu:20.04
이제 이 볼륨을 공유할 컨테이너를 만들어서 파일을 하나 써보자.
1
2
docker run -it --volumes-from datavol --name=datavol-1 ubuntu:20.04
echo 'datavol-1 container' > /data-volume/test-volume.txt
컨테이너에서 나왔다가 다시 붙고 싶다면 아래처럼 재시작하면 된다.
1
docker start -ai datavol-1
같은 볼륨을 공유하는 두 번째 컨테이너를 만들어보면, datavol-1에서 써둔 파일이 그대로 보인다.
1
2
3
docker run -it --volumes-from datavol --name=datavol-2 ubuntu:20.04
ls /data-volume
echo 'datavol-2 container' > /data-volume/test-volume.txt
datavol-1, datavol-2 두 컨테이너 모두 --volumes-from datavol 옵션으로 datavol 컨테이너가 갖고 있는 /data-volume 볼륨을 공유하고 있기 때문에, 한쪽에서 쓴 파일을 다른 쪽에서도 그대로 읽고 덮어쓸 수 있다.
9. 파일 시스템 마운트의 한계: 대체와 병합
바인드 마운트나 볼륨 마운트로 이미 컨테이너에 존재하는 경로를 덮으면, 마운트 대상이 디렉토리냐 파일이냐에 따라 동작이 다르다.
디렉토리 전체를 마운트하면 완전히 대체된다
컨테이너의 마운트 대상 디렉토리에 이미지 레이어가 만들어둔 파일이 들어있더라도, 그 경로에 호스트 디렉토리를 통째로 마운트하면 원래 있던 파일은 가려져서 접근할 수 없게 된다. 이미지 레이어에서 지워지는 것은 아니고 마운트된 디렉토리 뒤에 숨는 것뿐이지만, 컨테이너가 실행되는 동안에는 사용할 수 없는 것과 마찬가지다.
예를 들어 nginx 이미지는 /usr/share/nginx/html에 기본 index.html을 담고 있는데, 이 경로에 빈 호스트 디렉토리를 마운트하면 그 기본 페이지가 사라진 것처럼 보인다.
1
docker run -dit --name nginx-test -v ~/empty-dir:/usr/share/nginx/html nginx
그래서 초기 설정 파일이나 기본 콘텐츠가 들어있는 디렉토리를 통째로 마운트할 때는, 호스트 쪽 디렉토리에도 필요한 파일을 미리 채워둬야 컨테이너가 정상적으로 동작한다.
파일 하나만 마운트하면 병합처럼 보인다
반면 호스트의 파일 하나를 컨테이너 안에 있는 특정 파일 경로 하나에만 마운트하면, 디렉토리 자체가 통째로 교체되는 게 아니라 지정한 파일 하나만 호스트 것으로 바뀐다. 같은 디렉토리 안의 나머지 파일들은 이미지에서 온 그대로 남아있기 때문에, 결과적으로 이미지의 파일과 호스트의 파일이 한 디렉토리 안에 섞여 있는 것처럼 보인다.
1
docker run -dit --name nginx-conf -v ~/nginx.conf:/etc/nginx/nginx.conf nginx
이렇게 실행하면 /etc/nginx 안의 mime.types 같은 다른 이미지 파일들은 그대로 남고, nginx.conf만 호스트에서 가져온 파일로 바뀐다.
| 마운트 대상 | 동작 | 결과 |
|---|---|---|
| 디렉토리 → 디렉토리 | 대체(overwrite) | 기존 디렉토리 내용이 전부 가려짐 |
| 파일 → 디렉토리 내 특정 파일 | 교체(replace) | 지정한 파일만 바뀌고 나머지는 유지(병합처럼 보임) |
10. 정리
컨테이너의 데이터는 컨테이너가 삭제되면 함께 사라지므로, 유지되어야 할 데이터는 볼륨 마운트나 바인드 마운트로 컨테이너 생명주기 밖에 분리해둬야 한다. 볼륨 마운트는 Docker가 관리하는 영역을 쓰기 때문에 이식성이 좋고, 바인드 마운트는 호스트 경로를 그대로 쓰기 때문에 로컬 파일을 실시간으로 컨테이너에 반영하고 싶을 때 편리하다. 반대로 데이터를 아예 남기고 싶지 않다면 메모리에만 올라가는 tmpfs 마운트를 쓰면 된다.
MySQL 예제처럼 볼륨을 데이터베이스의 데이터 디렉토리에 연결해두면 컨테이너를 지웠다 다시 만들어도 데이터가 유지되고, --volumes-from을 쓰면 하나의 볼륨을 여러 컨테이너가 공유해서 데이터를 주고받을 수도 있다. 다만 마운트할 때 그 대상이 디렉토리냐 파일이냐에 따라 “완전히 대체”되는지 “부분적으로만 교체”되는지가 달라지므로, 특히 이미지에 기본 설정 파일이나 콘텐츠가 들어있는 경로를 마운트할 때는 이 차이를 미리 알아두는 것이 좋다.