이미지와 컨테이너의 근본적인 차이를 앞에서 봤었다.
이미지(Image)의 특성
- 이미지는 완전히 읽기 전용(Read-only)이다
- 한 번 빌드된 이미지는 절대 변경되지 않는다
- 이미지를 업데이트하려면 Dockerfile을 수정하고 완전히 재빌드해야 한다
- 이는 이미지의 불변성(Immutability)을 보장하여 일관성을 유지하는 데 도움이 된다
컨테이너(Container)의 특성
- 컨테이너는 이미지 위에 얇은 "읽기-쓰기 레이어(Read-Write Layer)"를 추가한다
- 이 레이어 덕분에 컨테이너는 파일을 읽고 쓸 수 있다
- 컨테이너 내부에서 파일을 생성, 수정, 삭제할 수 있지만, 실제 이미지는 변경되지 않는다
- 즉, 여러 컨테이너가 동일한 이미지를 공유하면서도 각자 독립적인 파일 시스템 변경을 가질 수 있다
도커 컨테이너는 기본적으로 일시적(ephemeral)이다.
- 컨테이너를 삭제하면 그 안의 데이터도 함께 사라진다.
- 볼륨(Volume)을 사용하면 데이터를 컨테이너 외부에 저장하여 영구적으로 보관할 수 있다.
- 마치 외장하드처럼 컨테이너가 사라져도 데이터를 안전하게 보관한다.
이 문서에서 다루는 내용은 아래오 ㅏ같다.
- Understanding Different Kinds of Data
- Images, Containers & Volumes
- Using Arguments & Environment Variables
Data Categories
도커에서 다루는 데이터는 용도와 저장 방식에 따라 3가지로 구분된다.

1. Application Code (애플리케이션 코드)
- 특성
- 읽기 전용(Read-only)
- 이미지에 저장
- 내용
- 소스 코드, 패키지, 실행 환경
- Dockerfile로 이미지를 빌드할 때 한 번 복사되면 끝
- 코드를 수정하려면 이미지를 다시 빌드해야 함
2. Temporary App Data (임시 데이터)
- 특성
- 읽기 + 쓰기 가능(Read-Write)
- 컨테이너에 저장
- 사라져도 괜찮음
- 내용
- 앱이 실행되는 동안만 필요한 데이터
- 사용자 입력값, 임시 계산 결과, 메모리 변수
- 컨테이너의 Read-Write 레이어에 저장됨
- 저장 위치
- 이미지 위에 추가되는 임시 레이어
- 이미지 자체는 변경하지 않고, 컨테이너만의 공간에서 수정
3. Persistent Data (영구 데이터)
- 특성
- 읽기 + 쓰기 가능(Read-Write)
- 컨테이너 + 볼륨 사용
- 절대 사라지면 안 됨!
- 내용
- 사용자 계정 정보, 데이터베이스, 업로드된 파일
- 컨테이너 중지/삭제/업데이트 후에도 유지되어야 함
- 해결 방법
- 볼륨(Volume) 사용
- 컨테이너와 별도로 관리되는 저장 공간
- 컨테이너에 연결하여 데이터 영속성 보장
그런데, 컨테이너만으로는 이 3번(영구 데이터)을 처리할 수 없다!
컨테이너의 'Read-Write Layer'가 존재하긴 하지만, 두 가지 문제가 있기 때문이다.
- 데이터 휘발성: 컨테이너가 삭제되면 데이터도 함께 날아간다. (영구 보존 불가)
- 격리성: 컨테이너는 호스트와 격리되어 있어, 내 컴퓨터(Host)에 있는 파일을 직접 읽거나 쓸 수 없다.
그래서 등장한 해결책이 바로 'Volumes'와 'Bind Mounts'다.
이들은 호스트 머신의 파일 시스템과 컨테이너를 연결해 주는 다리 역할을 한다.
Volumes (볼륨)
볼륨이란?
- 호스트 컴퓨터와 컨테이너를 연결하는 다리
- 데이터를 컨테이너 외부(호스트)에 저장
호스트 머신 컨테이너
/var/lib/docker/volumes/ /app/data/
└── my-volume/ ↕
↕ 실시간 동기화!
실시간 동기화!
COPY vs Volume
| 구분 | COPY 명령어 | Volume |
| 작동 시점 | 이미지 빌드 시 | 컨테이너 실행 시 |
| 동작 방식 | 로컬 -> 스냅샷 -> 이미지(일회성) | 호스트 <-> 컨테이너 (지속적) |
| 연결 특성 | 일회성 복사, 이후 연결 끊김 | 실시간 양방향 동기화 |
| 로컬 수정 | 이미지/컨테이너 반영 안 됨 | 컨테이너에 즉시 반영 |
| 컨테이너 수정 | 로컬에 반영 안 됨 | 호스트에 즉시 반영 |
| 컨테이너 삭제 | 데이터 손실 | 데이터 유지 |
볼륨은 3가지 타입이 있다.
1. Anonymous Volume (익명 볼륨)
생성 방법
# Dockerfile에서
VOLUME ["/app/feedback"]
또는
# docker run에서
docker run -v /app/data myimage
특징
- 컨테이너 내부 경로만 지정
- Docker가 자동으로 임의의 ID 생성
- 이름이 없어서 식별하기 어려움
생명주기
# --rm 옵션 사용 시
docker run --rm -v /app/data myimage
# → 컨테이너 중지 시 볼륨도 자동 삭제
# --rm 옵션 없이 사용 시
docker run -v /app/data myimage
# → 컨테이너 제거해도 볼륨은 남아있음 (수동 삭제 필요)
주요 용도
폴더 보호 (덮어쓰기 방지)
- Bind Mount가 /app 전체를 덮어쓰려 할 때
- /app/node_modules 는 익명 볼륨으로 보호되어 유지된다.
docker run \
-v /host/source:/app \ # Bind Mount: 전체 덮어씀
-v /app/node_modules \ # Anonymous Volume: 이 폴더만 보호
myimage
성능 향상
- 임시 데이터를 호스트 파일 시스템에서 직접 관리
- 컨테이너의 느린 Read-Write 레이어 우회
- 어차피 삭제될 데이터이므로 익명 볼륨 사용
docker run -v /app/temp myimage
데이터 영속성 목적으로는 부적합하다. 다만 특정 폴더 보호나 성능 최적화에 유용하다.
2. Named Volume
생성 방법
docker run -v feedback:/app/feedback myimage
# └─────┬─────┘ └─────┬──────┘
# 볼륨 이름 컨테이너 경로
특징
- 사람이 읽을 수 있는 이름으로 식별
- Docker가 관리하는 위치에 저장 (개발자는 정확한 위치 모름)
- 특정 컨테이너에 종속되지 않음
생명주기
# 컨테이너 제거해도 볼륨은 유지
docker stop mycontainer
docker rm mycontainer
# 볼륨 확인
docker volume ls
# DRIVER VOLUME NAME
# local feedback ← 여전히 존재
# 동일 볼륨으로 새 컨테이너 시작
docker run -v feedback:/app/feedback myimage
# → 이전 데이터 그대로 사용 가능!
# 수동 삭제 필요
docker volume rm feedback
데이터 공유
# 여러 컨테이너가 동일한 볼륨 공유
docker run -v shared-data:/app/data container1
docker run -v shared-data:/app/data container2
# → container1과 container2가 데이터 공유
주요 용도
- 데이터베이스 데이터 영구 보존
- 로그 파일 저장
- 사용자 업로드 파일 관리
- 여러 컨테이너 간 데이터 공유
PostgreSQL 예시
docker run \
-v postgres-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres
# 컨테이너 재생성해도 DB 데이터 유지됨
3. Bind Mount (바인드 마운트)
Named Volume과의 핵심 차이
- Named Volume: Docker가 관리하는 위치 (개발자 모름)
- Bind Mount: 개발자가 직접 경로 지정 (내 프로젝트 폴더)
생성 방법
docker run -v /absolute/path/on/host:/path/in/container myimage
# └──────────┬──────────┘ └────────┬────────┘
# 호스트 절대 경로 컨테이너 경로
규칙
- 호스트 경로는 반드시 절대 경로
- 상대 경로 사용 불가
# 올바른 사용
docker run -v /Users/john/myapp:/app myimage
docker run -v $(pwd):/app myimage # 현재 디렉토리
# 잘못된 사용
docker run -v ./myapp:/app myimage # 상대 경로 불가
docker run -v ../project:/app myimage # 상대 경로 불가
주요 용도: 실시간 코드 동기화
# Node.js 개발 환경
docker run \
-v /Users/john/my-node-app:/app \ # 소스 코드 동기화
-v /app/node_modules \ # node_modules 보호
-p 3000:3000 \
node-dev-image
# 효과:
# - 로컬에서 코드 수정 → 컨테이너에 즉시 반영
# - 이미지 재빌드 불필요
# - 개발 서버가 변경 감지하고 자동 재시작
동작 방식
호스트 머신 컨테이너
/Users/john/myapp/ /app/
├── app.py ├── app.py
├── config.py ◄────► ├── config.py
└── templates/ 실시간 └── templates/
동기화
호스트 수정 → 컨테이너 즉시 반영
컨테이너 수정 → 호스트 즉시 반영
사용 시 주의사항
- 적합한 경우
- 개발 중 소스 코드 실시간 동기화
- 설정 파일이 자주 변경될 때
- 컨테이너가 생성한 파일을 호스트에서 직접 확인
- 부적합한 경우
- 단순 데이터 영속화 목적 → Named Volume 사용
- 프로덕션 환경 → 컨테이너는 호스트와 격리되어야 함
정리
| 특성 | Named Volume | Anonymous Volume | Bind Mount |
| 문법 | name:/path | /path | /host/path:/path |
| 경로 지정 | Docker가 관리 | Docker가 관리 | 개발자가 지정 |
| 생명주기 | 수동 삭제 필요 | --rm시 자동 삭제 | 수동 삭제 필요 |
| 데이터 공유 | 가능 (여러 컨테이너) | 불가능 | 가능 (여러 컨테이너) |
| 영속성 | 컨테이너 독립적 | 컨테이너에 종속 | 컨테이너 독립적 |
| 주 용도 | 데이터 영속화 | 폴더 보호, 성능 향상 | 개발 중 동기화 |
| 프로덕션 | 적합 | 적합 | 부적합 |
| 개발 | 필요시 | 필요시 | 매우 유용 |
| 파일 접근 | 어려움 (Docker 관리) | 어려움 | 쉬움 (호스트에서 직접) |
node_modules와 경로 우선순위
Bind Mount를 사용할 때 가장 많이 겪는 문제가 있다. 바로 호스트 폴더가 컨테이너 폴더를 덮어쓰면서 발생하는 파일 소실 문제다.
문제 상황: node_modules가 사라진다?
Node.js 프로젝트를 개발할 때 다음과 같이 실행하면 에러가 발생한다.
docker run -p 3000:3000 -v $(pwd):/app my-node-image
# Error: Cannot find module 'express'
이유
- 이미지 빌드 시 npm install을 통해 /app/node_modules를 생성했다.
- docker run 시 Bind Mount(-v $(pwd):/app)가 실행된다.
- 내 로컬 컴퓨터(Host)에는 아직 node_modules가 없다.
- 빈 로컬 폴더가 컨테이너의 /app을 덮어씌워 버린다.
- 결과적으로 컨테이너 내부의 node_modules가 사라진다.
해결책: 익명 볼륨(Anonymous Volume) 활용
Docker의 "더 구체적인 경로가 우선한다(Longer Path Wins)"는 규칙을 이용한다.
docker run -p 3000:3000 \
-v $(pwd):/app \ # 1. Bind Mount (전체 덮어쓰기)
-v /app/node_modules \ # 2. Anonymous Volume (이 경로만 보호)
my-node-image
작동 원리
- Docker는 /app보다 /app/node_modules 경로가 더 길고 구체적이라고 판단한다.
- 따라서 /app 전체는 호스트와 동기화하지만, /app/node_modules 만큼은 컨테이너가 독자적으로 관리하는 익명 볼륨을 사용하게 된다.
- 이로써 로컬 소스 코드는 동기화하면서, 의존성 모듈은 지켜낼 수 있다.
보안 강화: 읽기 전용(Read-Only) 볼륨
Bind Mount는 기본적으로 양방향이다. 즉, 컨테이너 내부에서 실수로 파일을 삭제하면 내 컴퓨터(Host)의 파일도 삭제된다.
이를 방지하기 위해 Read-Only 설정을 사용한다.
사용법 (:ro)
경로 뒤에 :ro를 붙이면 컨테이너는 해당 경로를 읽을 수만 있고 수정할 수 없다.
docker run \
-v $(pwd):/app:ro \ # 소스 코드는 읽기만 가능하다.
my-node-image
주의사항: 쓰기가 필요한 폴더 처리
- 전체를 읽기 전용으로 만들면, 앱이 실행 중에 데이터를 써야 하는 폴더(예: /app/temp)에도 접근할 수 없게 된다.
- 이때도 구체적인 경로 우선순위를 이용해 해결한다.
docker run \
-v $(pwd):/app:ro \ # 1. 전체는 읽기 전용
-v /app/temp \ # 2. temp 폴더는 일반 익명 볼륨 (쓰기 가능)
my-node-image
이렇게 하면 소스 코드는 보호하면서(ro), 필요한 임시 폴더에는 데이터를 쓸 수 있다(rw).
환경 설정 관리: ARG vs ENV
애플리케이션의 포트 번호나 DB 비밀번호 같은 설정값은 하드코딩하지 않고 변수로 관리해야 한다. Docker에서는 ARG와 ENV 두 가지 방식을 제공한다.
1. ARG (Build Arguments)
- 시점: 이미지를 빌드(Build)하는 동안에만 유효하다.
- 용도: 버전 명시, 빌드 구조 변경 등.
- 특징: 이미지가 다 만들어지고 컨테이너가 실행될 때는 사라진다.
# Dockerfile
ARG DEFAULT_PORT=80
# 빌드 시점에 변수 사용 가능
EXPOSE $DEFAULT_PORT
2. ENV (Environment Variables)
- 시점: 빌드 시간 + 컨테이너 실행(Runtime) 시간 모두 유효하다.
- 용도: 애플리케이션 설정(DB 접속 정보, API 키 등).
- 특징: 컨테이너 내부의 코드(Node.js, Python 등)에서 process.env 등으로 접근 가능하다.
# Dockerfile
ENV PORT=80
# 앱 실행 시 접근 가능
CMD ["node", "server.js"]
ARG와 ENV 함께 사용하기 (빌드 최적화)
자주 바뀌는 설정값 때문에 매번 npm install 같은 무거운 작업을 다시 하는 것을 막기 위해 순서를 잘 배치해야 한다.
FROM node
WORKDIR /app
# 1. 변경이 적은 파일 먼저 복사 (캐시 활용)
COPY package.json .
RUN npm install
COPY . .
# 2. 자주 변경되는 환경변수는 나중에 선언
ARG DEFAULT_PORT=80
ENV PORT=$DEFAULT_PORT
EXPOSE $PORT
CMD ["node", "server.js"]
최종 정리: 어떤 데이터를 어디에 저장할까?
도커 데이터 관리는 결국 "이 데이터가 언제까지 살아있어야 하는가?를 결정하는 것이다.
| 데이터 종류 | 저장 방식 | 예시 | 특징 |
| 소스 코드 | Bind Mount | /app (개발 중) | 내 컴퓨터에서 코드 수정 시 즉시 반영 |
| 의존성 패키지 | Anonymous Volume | /app/node_modules | Bind Mount가 덮어쓰는 것을 방지 |
| 영구 데이터 | Named Volume | /app/data | DB 파일 등, 컨테이너 삭제 후에도 보존 |
| 설정 값 | ENV | PORT, API_KEY | 코드 수정 없이 실행 환경 제어 |
개발 vs 프로덕션 전략
- 개발 환경: Bind Mount를 적극 사용하여 실시간 코드 반영을 우선시한다.
- 프로덕션 환경: Bind Mount를 제거하고, COPY로 코드를 이미지 안에 완전히 포함시켜 배포한다(이식성과 보안). 중요 데이터는 Named Volume으로 관리한다.
'Infra > Docker' 카테고리의 다른 글
| 컨테이너 배포: 수동(자체 관리형) (0) | 2025.12.14 |
|---|---|
| Docker Networks (0) | 2025.12.12 |
| Docker Image & Container 명령어 정리 (0) | 2025.12.12 |
| Container의 근반이 되는 기술에 대해 알아보자(완) UnionFS (0) | 2025.12.11 |
| Container의 근반이 되는 기술에 대해 알아보자(2) cgroup (0) | 2025.12.11 |