도커(Docker)를 활용해 데이터베이스를 구축할 때 가장 먼저 맞닥뜨리는 핵심 문제는 “컨테이너를 삭제하거나 재시작했을 때 내부 데이터가 유지되는가?”입니다. 도커 컨테이너는 기본적으로 상태를 저장하지 않는 Stateless(무상태성) 특성을 지니고 있어, 별도의 저장소 매핑 설정을 하지 않으면 컨테이너 종료 및 삭제 시 생성된 테이블과 레코드가 모두 사라집니다.
MySQL과 같은 RDBMS를 도커 환경에서 안정적으로 운용하기 위해서는 호스트 운영체제의 파일 시스템과 컨테이너 내부의 데이터 디렉터리(/var/lib/mysql)를 연결하는 도커 볼륨(Docker Volume) 기술이 필수적입니다. 이 글에서는 초보 개발자가 MySQL 컨테이너를 올바르게 생성하고, 데이터 영구 보존(Persistence)을 구성하는 실전 방법을 명확하게 정리합니다.
목차
- 1. 도커 컨테이너 데이터 휘발성과 볼륨(Volume)의 필요성
- 2. Named Volume vs Bind Mount 방식 비교
- 3. [실전 1] Named Volume을 이용한 MySQL 컨테이너 생성
- 4. [실전 2] Bind Mount를 이용한 로컬 디렉터리 연동
- 5. Docker Compose를 통한 표준화된 MySQL 운용
- 6. 데이터 영구 저장 검증 및 주의사항
- 7. 자주 묻는 질문 (FAQ)
- 8. 출처 및 참고 사이트

1. 도커 컨테이너 데이터 휘발성과 볼륨(Volume)의 필요성
도커 컨테이너의 기본 스토리지 계층은 유니온 파일 시스템(Union File System) 기반의 쓰기 가능한 임시 레이어(Writable Container Layer)로 구성됩니다. 이 계층은 컨테이너 라이프사이클과 완전히 결합되어 있습니다.
- 컨테이너 중지(docker stop): 데이터가 유지되지만, 재시작하지 않으면 접근할 수 없습니다.
- 컨테이너 삭제(docker rm): 쓰기 레이어가 영구적으로 파괴되며, 컨테이너 내부에서 발생한 모든 변경사항이 완전히 소멸합니다.
핵심 저장 경로: 공식 MySQL Docker 이미지는 데이터베이스 실제 파일들을 컨테이너 내부의 /var/lib/mysql 경로에 저장합니다. 따라서 호스트의 저장소와 이 경로를 볼륨으로 연결(마운트)해야만 컨테이너 수명 주기와 무관하게 데이터를 안전하게 보존할 수 있습니다.

2. Named Volume vs Bind Mount 방식 비교
도커에서 데이터를 영구 저장하는 대표적인 방식은 명명된 볼륨(Named Volume)과 바인드 마운트(Bind Mount) 두 가지가 있습니다. 프로젝트 성격과 인프라 운영 환경에 따라 적합한 방식을 선택해야 합니다.
구분 | Named Volume (도커 관리형) | Bind Mount (호스트 경로 지정형) |
|---|---|---|
| 저장 위치 | 도커 관리 영역 (/var/lib/docker/volumes/...) | 사용자가 지정한 호스트의 절대/상대 경로 |
| 호스트 파일 직접 수정 | 어려움 (도커 데몬이 권한 제어) | 매우 쉬움 (IDE, 텍스트 편집기로 접근 가능) |
| 권한 및 격리성 | OS 간 호환성이 높고 권한 충돌 위험 낮음 | 호스트 사용자와 컨테이너 UID/GID 권한 충돌 가능 |
| I/O 성능 | 도커 엔진 최적화로 안정적인 성능 제공 | OS 및 파일 시스템 구성에 따라 성능 편차 발생 가능 |
| 권장 사용 용도 | 운영 환경(Production), 순수 DB 데이터 보존 | 개발 환경, 설정 파일(my.cnf), 초기 SQL 스크립트 마운트 |

3. [실전 1] Named Volume을 이용한 MySQL 컨테이너 생성
실제 운영 환경이나 안정적인 로컬 DB 환경을 구축할 때는 도커가 직접 수명 주기를 제어하는 Named Volume 방식이 권장됩니다.
단계 1: 도커 볼륨 생성
데이터를 보관할 명명된 볼륨을 먼저 생성합니다.
docker volume create mysql_data
생성된 볼륨은 docker volume ls 명령어로 확인할 수 있습니다.
단계 2: MySQL 컨테이너 실행 (볼륨 마운트)
생성한 볼륨을 MySQL 내부 데이터 저장 경로(/var/lib/mysql)와 연결하여 컨테이너를 백그라운드(-d)로 실행합니다.
docker run -d \
--name mysql-server \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=your_secure_password \
-e MYSQL_DATABASE=mydb \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=appuser_password \
-v mysql_data:/var/lib/mysql \
--restart unless-stopped \
mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
명령어 주요 옵션 상세 설명
-d: 백그라운드(디태치) 모드로 컨테이너를 실행합니다.--name mysql-server: 컨테이너 이름을 식별하기 쉽게 지정합니다.-p 3306:3306: 호스트의 3306 포트와 컨테이너 내부 3306 포트를 포워딩합니다.-e MYSQL_ROOT_PASSWORD=...: MySQL root 관리자 비밀번호를 설정합니다 (필수).-e MYSQL_DATABASE=...: 컨테이너 최초 초기화 시 자동 생성할 기본 데이터베이스명입니다.-v mysql_data:/var/lib/mysql: 호스트의mysql_data볼륨을 컨테이너 내부 데이터 경로에 마운트합니다.--character-set-server=utf8mb4: 이모지 및 다국어 지원을 위한 기본 문자 집합을 설정합니다.

4. [실전 2] Bind Mount를 이용한 로컬 디렉터리 연동
개발 환경에서 호스트의 특정 폴더(예: 프로젝트 내부 폴더)에 데이터 파일이나 설정 파일(my.cnf)을 직접 두고 관리하고 싶을 때는 Bind Mount 방식을 사용합니다.
단계 1: 호스트 로컬 디렉터리 준비
# 프로젝트 디렉터리 내에 데이터 저장 폴더 생성
mkdir -p ./mysql-data
mkdir -p ./mysql-init
단계 2: 호스트 경로 바인드 마운트로 컨테이너 실행
docker run -d \
--name mysql-bind-server \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=your_secure_password \
-v $(pwd)/mysql-data:/var/lib/mysql \
-v $(pwd)/mysql-init:/docker-entrypoint-initdb.d \
mysql:8.0
초기화 스크립트 자동 실행 기능:/docker-entrypoint-initdb.d 디렉터리에 .sql 또는 .sh 파일을 마운트해 두면, 컨테이너가 최초로 데이터베이스를 초기화할 때 알파벳 순서대로 자동 실행됩니다. (기존 데이터 디렉터리가 비어 있을 때만 1회 동작)

5. Docker Compose를 통한 표준화된 MySQL 운용
실제 개발 및 배포 환경에서는 매번 긴 docker run 명령어를 입력하는 대신, docker-compose.yml 파일을 정의하여 인프라 설정을 코드로 관리(IaC)하는 방식이 표준입니다.
services:
database:
image: mysql:8.0
container_name: dev-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpassword123
MYSQL_DATABASE: servicedb
MYSQL_USER: devuser
MYSQL_PASSWORD: devpassword123
TZ: Asia/Seoul
ports:
- "3306:3306"
volumes:
- mysql_storage:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
mysql_storage:
driver: local
위 파일을 작성한 후 프로젝트 루트에서 다음 명령어로 즉시 실행할 수 있습니다.
# 백그라운드 실행 docker compose up -d실행 상태 확인 docker compose ps 컨테이너 종료 (볼륨 데이터는 유지됨) docker compose down [주의] 볼륨까지 완전 삭제할 때만 -v 플래그 사용docker compose down -v
6. 데이터 영구 저장 검증 및 주의사항
볼륨이 정상적으로 동작하여 데이터가 영구 보존되는지 확인하는 검증 절차입니다.
- 데이터베이스 접속 및 데이터 삽입:
docker exec -it mysql-server mysql -u root -p # 비밀번호 입력 후 진입USE mydb;
CREATE TABLE test_users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_users (name) VALUES ('Docker');
SELECT * FROM test_users;
EXIT;
컨테이너 강제 삭제:docker rm -f mysql-server
동일한 볼륨으로 새 컨테이너 생성:docker run -d
--name mysql-server-new
-p 3306:3306
-e MYSQL_ROOT_PASSWORD=your_secure_password
-v mysql_data:/var/lib/mysql
mysql:8.0
데이터 잔존 여부 확인:docker exec -it mysql-server-new mysql -u root -p -e "SELECT * FROM mydb.test_users;"
기존에 입력했던Docker레코드가 그대로 조회된다면 볼륨 영구 저장이 성공적으로 적용된 것입니다.
7. 자주 묻는 질문 (FAQ)
Q. docker rm으로 컨테이너를 지우면 볼륨 데이터도 함께 사라지나요?
아닙니다. docker rm <컨테이너명> 명령어는 컨테이너 본체만 제거하며 연결된 볼륨(Named Volume)은 삭제되지 않고 호스트에 그대로 보존됩니다. 볼륨까지 완전히 삭제하려면 명시적으로 docker volume rm <볼륨명> 또는 docker compose down -v 명령어를 실행해야 합니다.
Q. 호스트 바인드 마운트 사용 시 ‘Permission Denied’ 권한 오류가 발생합니다.
MySQL 컨테이너 내부 프로세스는 보안상 mysql 전용 계정(통상 UID 999 또는 1001)으로 구동됩니다. 호스트의 디렉터리 소유권이 root나 현재 호스트 사용자로만 제한되어 있는 경우 쓰기 권한 충돌이 발생할 수 있습니다. 호스트 경로의 권한을 chmod 777 ./mysql-data 또는 chown -R 999:999 ./mysql-data로 조정하여 권한을 부여해야 합니다.
Q. 컨테이너 내부 MySQL 데이터를 외부로 덤프(백업)하려면 어떻게 하나요?
docker exec 명령어를 사용해 컨테이너 내부의 mysqldump 유틸리티 결과를 호스트 파일로 직접 리다이렉션하여 백업할 수 있습니다.docker exec mysql-server mysqldump -u root -p'비밀번호' mydb > backup.sql
결론: 안정적인 컨테이너 DB 운영 전략
도커를 활용한 MySQL 데이터베이스 구성은 개발 환경의 표준화와 빠른 배포를 가능하게 하지만, 데이터의 비휘발성 보장이 선행되지 않으면 치명적인 데이터 유실로 이어질 수 있습니다.
- 실운영 환경 및 장기 보존이 필요한 데이터는 Named Volume을 기본으로 사용하세요.
- 설정 파일(
my.cnf) 커스터마이징이나 초기 DDL 스크립트 실행에는 Bind Mount를 결합하여 활용하는 구조가 가장 권장됩니다. - 반복되는 환경 설정을 방지하기 위해
docker-compose.yml파일로 형상 관리하는 습관을 들이시기 바랍니다.