이미지와 컨테이너는 정확히 무엇이 다른가
"이미지는 붕어빵 틀, 컨테이너는 붕어빵이다." Docker를 설명할 때 흔히 나오는 비유입니다. 틀렸다고 할 순 없지만, 이 비유만 믿고 넘어가면 나중에 "왜 컨테이너 안에서 파일을 수정했는데 이미지를 새로 만들면 사라지지"라는 질문에 답하지 못합니다. 실제로 무슨 일이 벌어지는지 한 단계 더 들어가 보겠습니다.
이미지는 읽기 전용 레이어의 스택입니다
Docker 이미지는 하나의 파일이 아니라 여러 개의 읽기 전용 레이어가 쌓인 것입니다. 각 레이어는 이전 레이어와의 차이(diff)만 담고 있습니다. 예를 들어 다음과 같은 Dockerfile이 있다면,
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
COPY app.py /app/app.py
CMD ["python3", "/app/app.py"]이건 대략 다음과 같은 레이어들을 만듭니다.
flowchart BT L1["레이어 1: ubuntu:22.04 베이스"] --> L2["레이어 2: curl 설치"] L2 --> L3["레이어 3: app.py 복사"] L3 --> L4["실행 설정: CMD"]
이 레이어들은 한 번 만들어지면 절대 바뀌지 않습니다. 그리고 여러 이미지가 같은 베이스 레이어를 공유할 수 있습니다. ubuntu:22.04 위에 만든 이미지가 100개 있어도, 디스크에는 그 베이스 레이어가 한 번만 저장됩니다. docker images 를 실행했을 때 이미지 크기의 합이 실제 디스크 사용량과 다른 이유가 이겁니다.
컨테이너는 이미지 위에 얹은 쓰기 가능 레이어
docker run 을 실행하면 Docker는 이미지의 읽기 전용 레이어들 위에 얇은 쓰기 가능 레이어를 하나 추가합니다. 컨테이너 안에서 파일을 만들거나 수정하는 모든 작업은 전부 이 얇은 레이어에만 기록됩니다. 아래에 있는 이미지 레이어는 절대 건드리지 않습니다.
flowchart BT I["이미지 레이어들 (읽기 전용, 공유됨)"] --> W["쓰기 가능 레이어 (컨테이너 전용)"]
이 구조가 설명하는 것들이 있습니다.
- 같은 이미지로 컨테이너 10개를 띄워도 이미지 자체는 하나만 디스크에 있습니다. 각 컨테이너는 자기만의 얇은 쓰기 레이어만 추가로 가집니다. 그래서 컨테이너를 여러 개 띄우는 비용이 VM보다 훨씬 쌉니다.
- 컨테이너 안에서 파일을 지우거나 수정해도 이미지는 전혀 바뀌지 않습니다.
docker run ubuntu rm -rf /etc를 실행하고 컨테이너를 삭제한 뒤 같은 이미지로 새 컨테이너를 띄우면,/etc는 멀쩡히 돌아와 있습니다. 변경 사항은 그 컨테이너의 쓰기 레이어와 함께 사라졌기 때문입니다. - 컨테이너를 삭제하면(
docker rm) 쓰기 가능 레이어도 함께 사라집니다. 컨테이너 안에서 만든 로그 파일, 업로드한 파일, DB 데이터는 컨테이너와 운명을 같이 합니다. 이건 다음 장에서 다룰 "왜 데이터가 사라졌는가" 문제로 이어집니다.
그래서 이미지 하나 vs 컨테이너 여러 개, 라는 관계가 성립합니다
붕어빵 비유로 돌아가면, 컨테이너 여러 개가 같은 이미지에서 나온다는 점까지는 맞습니다. 다만 "붕어빵은 한 번 구우면 끝"이라는 이미지와 달리, 컨테이너는 실행 중에 계속 상태가 바뀌는 살아있는 프로세스입니다. 더 정확한 비유를 원한다면 이렇게 말하겠습니다: **이미지는 프로그램(정적인 실행 파일), 컨테이너는 프로세스(실행 중인 인스턴스)**입니다. 실제로 컨테이너는 리눅스 관점에서 그냥 격리된 프로세스입니다.
이 관점에서 보면 자연스럽게 따라오는 명령어들이 있습니다.
docker images # 디스크에 있는 이미지 목록 (프로그램 파일 목록과 비슷)
docker ps # 실행 중인 컨테이너 목록 (실행 중인 프로세스 목록과 비슷)
docker ps -a # 멈춘 컨테이너까지 전부
docker rm <container> # 컨테이너(프로세스 + 쓰기 레이어) 삭제
docker rmi <image> # 이미지(읽기 전용 레이어들) 삭제여기서 초보자가 자주 걸려 넘어지는 지점 하나. docker rmi 는 그 이미지로 만든 컨테이너가 하나라도 남아 있으면 실패합니다("image is being used by container..."). 이미지를 지우려면 먼저 그 이미지로 만든 컨테이너부터 지워야 합니다. 순서가 반대라는 걸 깨닫는 데 저도 꽤 걸렸습니다.
컨테이너 안에서 수정한 걸 이미지로 남기고 싶다면
가끔 "컨테이너에 들어가서 이것저것 설치하고 고친 다음, 그 상태를 이미지로 저장하고 싶다"는 상황이 생깁니다. docker commit 이 그걸 해줍니다.
docker commit my-container my-new-image:v1기술적으로는 됩니다. 하지만 실무에서는 거의 쓰지 않는 방법입니다. 이렇게 만든 이미지는 "누가 컨테이너 안에서 뭘 했는지"가 기록에 안 남아서, 나중에 왜 이렇게 만들어졌는지 아무도 재현할 수 없습니다. Dockerfile을 텍스트로 작성해서 버전 관리하는 게 사실상 유일하게 정석으로 통하는 방법이고, 다음 장에서 그걸 다룹니다.