🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

컨테이너와 Docker 기초 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 28. · 22 Views

컨테이너와 Docker 기초 완벽 가이드

컨테이너 기술의 핵심 개념부터 Docker를 활용한 실무 배포까지 단계별로 배웁니다. 가상머신과의 차이점, 이미지와 컨테이너의 관계, Dockerfile 작성법, 그리고 포트 포워딩과 볼륨 마운트까지 초급 개발자를 위해 쉽게 설명합니다.


목차

  1. 컨테이너_vs_가상머신_차이점
  2. Docker_이미지와_컨테이너_개념
  3. Dockerfile_작성_기초
  4. docker_build로_이미지_빌드
  5. docker_run으로_컨테이너_실행
  6. 포트_포워딩과_볼륨_마운트

1. 컨테이너 vs 가상머신 차이점

김개발 씨는 신규 프로젝트 배포를 앞두고 있었습니다. 선배가 "이번엔 Docker로 배포하자"고 했는데, 예전에 VirtualBox로 가상머신 만들어본 것과 뭐가 다른지 도통 감이 오지 않았습니다.

둘 다 격리된 환경을 만드는 건데, 왜 굳이 Docker를 쓰는 걸까요?

**가상머신(VM)**은 하드웨어를 통째로 가상화하여 완전한 운영체제를 실행하는 방식입니다. 반면 컨테이너는 운영체제의 커널을 공유하면서 애플리케이션만 격리하는 가벼운 방식입니다.

마치 아파트 전체를 빌리는 것과 원룸 하나만 빌리는 것의 차이와 같습니다. 컨테이너는 시작 속도가 빠르고 자원 효율이 높아 현대 개발 환경에서 널리 사용됩니다.

다음 코드를 살펴봅시다.

# 가상머신 방식의 구조 (개념적 표현)
# [App A] [App B] [App C]
# [Guest OS] [Guest OS] [Guest OS]  <- 각각 OS 필요
# [Hypervisor]
# [Host OS]
# [Hardware]

# 컨테이너 방식의 구조 (개념적 표현)
# [App A] [App B] [App C]
# [Container Runtime (Docker)]  <- OS 커널 공유
# [Host OS]
# [Hardware]

# 실제로 컨테이너 실행 시간 확인
time docker run --rm hello-world

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 오늘 팀 회의에서 선배 박시니어 씨가 "이제 가상머신 대신 Docker 컨테이너로 배포합니다"라고 선언했습니다.

김개발 씨는 고개를 갸웃했습니다. 둘 다 격리된 환경을 만드는 건데, 뭐가 그렇게 다른 걸까요?

박시니어 씨가 화이트보드 앞으로 다가갔습니다. "자, 비유를 들어볼게요.

가상머신은 마치 주택을 통째로 빌리는 것과 같아요." 주택을 빌리면 주방, 화장실, 거실, 침실이 모두 따라옵니다. 내가 원룸만 필요해도 집 전체에 대한 비용을 지불해야 합니다.

가상머신도 마찬가지입니다. 작은 웹 서버 하나를 돌리려고 해도 운영체제 전체를 설치해야 합니다.

Windows든 Linux든 수 기가바이트의 OS가 필요합니다. "반면에 컨테이너는 원룸을 빌리는 것과 같아요." 박시니어 씨가 말을 이었습니다.

원룸은 건물의 기반 시설을 다른 입주자들과 공유합니다. 전기, 수도, 엘리베이터를 함께 쓰면서도 내 공간은 확실히 분리되어 있습니다.

컨테이너도 호스트 운영체제의 커널을 공유하면서 애플리케이션만 격리합니다. 그래서 수 기가바이트가 아닌 수십 메가바이트면 충분합니다.

이 차이가 왜 중요할까요? 실제 현업에서는 서버 한 대에 여러 애플리케이션을 실행해야 합니다.

가상머신 방식이라면 서버 하나에 VM 세 개를 띄우면 OS도 세 개가 필요합니다. 각각 2GB라고 가정하면 OS만으로 6GB를 소비합니다.

부팅 시간도 각각 몇 분씩 걸립니다. 리소스 낭비가 심각합니다.

컨테이너는 다릅니다. 같은 서버에서 컨테이너 세 개를 띄워도 OS 커널은 하나만 있으면 됩니다.

각 컨테이너는 필요한 라이브러리와 애플리케이션만 담고 있어 가볍습니다. 시작 시간도 초 단위입니다.

보안 측면에서 두 기술은 서로 다른 접근 방식을 취합니다. 가상머신은 하드웨어 수준에서 완전히 격리되어 있어 보안성이 높습니다.

한 VM이 해킹당해도 다른 VM에 영향을 주기 어렵습니다. 컨테이너는 커널을 공유하므로 이론적으로는 격리 수준이 낮지만, 리눅스 네임스페이스와 cgroups 같은 기술로 충분한 격리를 제공합니다.

그렇다면 가상머신은 이제 쓸모없는 걸까요? 그렇지 않습니다.

완전히 다른 운영체제가 필요할 때는 여전히 가상머신이 필요합니다. 예를 들어 Mac에서 Windows를 돌리거나, 보안이 극도로 중요한 금융 시스템에서는 가상머신을 선택합니다.

각 기술은 적절한 용도가 있습니다. 김개발 씨가 고개를 끄덕였습니다.

"아, 그래서 Docker가 인기 있는 거군요! 같은 서버 자원으로 더 많은 서비스를 돌릴 수 있으니까요." 박시니어 씨가 웃으며 답했습니다.

"정확해요. 게다가 개발 환경과 운영 환경을 동일하게 맞출 수 있어서 '내 컴퓨터에선 되는데...' 같은 문제도 사라지죠." 컨테이너 기술을 이해하면 현대 개발 환경의 핵심을 파악한 것입니다.

다음으로는 Docker의 핵심 개념인 이미지와 컨테이너에 대해 알아보겠습니다.

실전 팁

💡 - 컨테이너는 "가벼운 격리"를 원할 때, 가상머신은 "완전한 격리"가 필요할 때 선택하세요

  • 개발 환경에서는 빠른 시작 속도 때문에 컨테이너가 압도적으로 편리합니다

2. Docker 이미지와 컨테이너 개념

김개발 씨가 Docker를 설치하고 첫 명령어를 입력했습니다. docker run을 실행했더니 "이미지를 다운로드한다"는 메시지가 나왔습니다.

이미지? 컨테이너?

뭐가 이미지고 뭐가 컨테이너인지 헷갈리기 시작했습니다.

Docker 이미지는 컨테이너를 만들기 위한 읽기 전용 템플릿입니다. 마치 붕어빵 틀과 같습니다.

컨테이너는 이 이미지를 기반으로 실제로 실행되는 인스턴스입니다. 붕어빵 틀에서 찍어낸 실제 붕어빵과 같습니다.

하나의 이미지로 여러 개의 동일한 컨테이너를 만들 수 있습니다.

다음 코드를 살펴봅시다.

# 이미지 목록 확인
docker images

# Docker Hub에서 nginx 이미지 다운로드
docker pull nginx:latest

# 이미지로부터 컨테이너 생성 및 실행
docker run --name my-nginx -d nginx:latest

# 실행 중인 컨테이너 목록 확인
docker ps

# 같은 이미지로 또 다른 컨테이너 생성
docker run --name my-nginx-2 -d nginx:latest

# 컨테이너 중지 및 삭제
docker stop my-nginx
docker rm my-nginx

점심시간, 박시니어 씨가 김개발 씨에게 물었습니다. "Docker 공부는 잘 되고 있어요?" 김개발 씨가 머리를 긁적였습니다.

"이미지랑 컨테이너가 헷갈려요. 둘 다 비슷해 보이는데 뭐가 다른 건가요?" 박시니어 씨가 근처 붕어빵 가게를 가리켰습니다.

"저 붕어빵 가게 보이죠? 이미지와 컨테이너의 관계를 완벽하게 설명해줄 수 있어요." 붕어빵 가게에는 붕어빵 틀이 있습니다.

이 틀은 붕어빵의 모양을 정의합니다. 어떤 재료가 들어가고, 어떤 형태로 나오는지가 이미 결정되어 있습니다.

Docker에서 이 틀이 바로 이미지입니다. 그리고 틀에서 찍어나온 실제 붕어빵이 있습니다.

같은 틀에서 나왔지만 각각 독립적인 존재입니다. 하나를 먹어도 다른 붕어빵에 영향이 없습니다.

이것이 바로 컨테이너입니다. "아!" 김개발 씨의 눈이 반짝였습니다.

"그러면 nginx 이미지 하나로 nginx 컨테이너를 여러 개 만들 수 있는 거네요?" "정확해요." 박시니어 씨가 고개를 끄덕였습니다. 이미지는 읽기 전용입니다.

한번 만들어지면 변경되지 않습니다. 마치 붕어빵 틀을 매번 새로 만들지 않는 것처럼요.

이미지 안에는 운영체제 기반 파일, 애플리케이션 코드, 필요한 라이브러리가 모두 레이어 형태로 쌓여있습니다. 레이어 구조는 Docker의 핵심 장점 중 하나입니다.

예를 들어 Ubuntu 기반 이미지를 여러 개 사용한다면, Ubuntu 레이어는 한 번만 다운로드됩니다. 이후 이미지들은 이 레이어를 공유하므로 디스크 공간을 절약할 수 있습니다.

컨테이너는 이미지 위에 쓰기 가능한 레이어를 얹은 것입니다. 컨테이너 안에서 파일을 수정하거나 생성하면 이 쓰기 레이어에 기록됩니다.

원본 이미지는 그대로 유지됩니다. 실무에서 이 개념이 왜 중요할까요?

배포 환경을 생각해봅시다. 개발자가 만든 이미지를 테스트 서버와 운영 서버에 배포합니다.

동일한 이미지에서 컨테이너를 실행하므로 "테스트에선 됐는데 운영에선 안 돼요" 같은 문제가 사라집니다. 환경 차이로 인한 버그가 원천 차단됩니다.

또한 스케일 아웃도 쉬워집니다. 트래픽이 늘어나면 같은 이미지로 컨테이너를 추가로 띄우면 됩니다.

로드밸런서 뒤에 동일한 컨테이너 열 개를 배치하는 것도 순식간입니다. 주의할 점이 있습니다.

컨테이너가 삭제되면 쓰기 레이어도 함께 사라집니다. 컨테이너 안에서 생성한 데이터를 보존하려면 나중에 배울 볼륨을 사용해야 합니다.

김개발 씨가 정리했습니다. "이미지는 설계도, 컨테이너는 그 설계도로 지은 집이네요.

설계도 하나로 집을 여러 채 지을 수 있고, 한 집이 무너져도 다른 집은 멀쩡하고요." 박시니어 씨가 엄지를 치켜세웠습니다. "완벽한 이해예요!"

실전 팁

💡 - docker images로 로컬에 저장된 이미지를, docker ps로 실행 중인 컨테이너를 확인하세요

  • 컨테이너를 삭제해도 이미지는 남아있어 다시 컨테이너를 만들 수 있습니다

3. Dockerfile 작성 기초

김개발 씨는 자신만의 Node.js 애플리케이션을 Docker로 실행하고 싶어졌습니다. 하지만 Docker Hub에는 순수 Node.js 이미지만 있을 뿐, 자신의 코드가 포함된 이미지는 없었습니다.

어떻게 나만의 이미지를 만들 수 있을까요?

Dockerfile은 Docker 이미지를 만들기 위한 설계도입니다. 마치 요리 레시피처럼 어떤 베이스 이미지를 사용하고, 어떤 파일을 복사하고, 어떤 명령어를 실행할지 순서대로 적어놓은 문서입니다.

Docker는 이 Dockerfile을 읽어 자동으로 이미지를 생성합니다.

다음 코드를 살펴봅시다.

# 베이스 이미지 지정 (Node.js 20 버전)
FROM node:20-alpine

# 작업 디렉토리 설정
WORKDIR /app

# 패키지 파일 먼저 복사 (캐시 활용)
COPY package*.json ./

# 의존성 설치
RUN npm install

# 소스 코드 복사
COPY . .

# 애플리케이션 포트 명시
EXPOSE 3000

# 컨테이너 시작 시 실행할 명령어
CMD ["node", "app.js"]

점심 식사 후, 김개발 씨는 본격적으로 Dockerfile을 작성하기로 했습니다. 박시니어 씨가 옆에서 조언을 해주기로 했습니다.

"Dockerfile은 요리 레시피라고 생각하면 돼요." 박시니어 씨가 설명을 시작했습니다. 레시피에는 순서가 있습니다.

먼저 재료를 준비하고, 손질하고, 조리하고, 마지막에 플레이팅을 합니다. Dockerfile도 마찬가지입니다.

위에서 아래로 순서대로 실행됩니다. 가장 먼저 나오는 FROM 명령어는 베이스 이미지를 지정합니다.

요리로 치면 "오늘의 요리는 파스타입니다. 면부터 시작합니다"와 같습니다.

node:20-alpine은 Node.js 20 버전이 설치된 가벼운 리눅스 이미지입니다. alpine은 경량 리눅스 배포판으로, 이미지 크기를 최소화할 수 있습니다.

WORKDIR은 작업 디렉토리를 설정합니다. 마치 요리사가 도마를 정리하고 작업 공간을 마련하는 것과 같습니다.

이후의 모든 명령어는 이 디렉토리를 기준으로 실행됩니다. 다음으로 COPY 명령어가 등장합니다.

호스트 컴퓨터의 파일을 이미지 안으로 복사합니다. 여기서 중요한 패턴이 있습니다.

"왜 package.json을 먼저 복사하고, 나중에 전체 소스를 복사하나요?" 김개발 씨가 물었습니다. 박시니어 씨가 미소를 지었습니다.

"아주 좋은 질문이에요. 레이어 캐싱 때문이에요." Docker는 각 명령어를 레이어로 저장합니다.

파일이 변경되지 않으면 해당 레이어를 재사용합니다. package.json은 자주 바뀌지 않지만 소스 코드는 자주 바뀝니다.

package.json을 먼저 복사하고 npm install을 실행하면, 소스 코드만 바뀌었을 때 npm install을 다시 실행하지 않아도 됩니다. 빌드 시간이 크게 단축됩니다.

RUN 명령어는 이미지를 빌드하는 시점에 실행됩니다. npm install로 의존성을 설치하면 그 결과가 이미지에 포함됩니다.

컨테이너를 실행할 때마다 설치할 필요가 없습니다. EXPOSE는 이 컨테이너가 어떤 포트를 사용하는지 문서화합니다.

실제로 포트를 열지는 않지만, 다른 개발자나 도구가 이 정보를 참조할 수 있습니다. 마지막으로 CMD는 컨테이너가 시작될 때 실행할 명령어를 지정합니다.

RUN과 다른 점은, RUN은 빌드 시점에 실행되고 CMD는 컨테이너 실행 시점에 실행된다는 것입니다. "정리하면, FROM으로 기반을 잡고, WORKDIR로 작업 공간을 만들고, COPY와 RUN으로 필요한 것들을 준비하고, CMD로 실행 방법을 알려주는 거네요." 김개발 씨가 말했습니다.

실무에서는 몇 가지 추가적인 명령어도 자주 사용됩니다. ENV로 환경변수를 설정하고, ARG로 빌드 시점 변수를 전달하고, ENTRYPOINT로 고정 실행 명령을 지정할 수 있습니다.

하지만 처음에는 오늘 배운 기본 명령어만으로도 대부분의 상황을 처리할 수 있습니다.

실전 팁

💡 - Dockerfile 이름은 확장자 없이 정확히 'Dockerfile'로 작성하세요

  • 레이어 캐싱을 활용하려면 자주 바뀌는 파일은 나중에 COPY하세요

4. docker build로 이미지 빌드

김개발 씨는 Dockerfile을 완성했습니다. 이제 이 레시피로 실제 이미지를 만들 차례입니다.

터미널을 열고 docker build 명령어를 입력하려는데, 옵션이 너무 많아 막막해졌습니다. 어떻게 하면 이미지를 제대로 빌드할 수 있을까요?

docker build 명령어는 Dockerfile을 읽어 이미지를 생성합니다. -t 옵션으로 이미지에 이름과 태그를 붙일 수 있습니다.

빌드 컨텍스트로 지정한 디렉토리의 모든 파일이 Docker 데몬으로 전송되므로, .dockerignore 파일로 불필요한 파일을 제외하는 것이 좋습니다.

다음 코드를 살펴봅시다.

# 기본 빌드 (현재 디렉토리의 Dockerfile 사용)
docker build -t my-app:1.0 .

# 특정 Dockerfile 지정
docker build -f Dockerfile.prod -t my-app:prod .

# 빌드 진행 상황 상세 출력
docker build --progress=plain -t my-app:1.0 .

# 빌드 인자 전달
docker build --build-arg NODE_ENV=production -t my-app:prod .

# 캐시 없이 새로 빌드
docker build --no-cache -t my-app:1.0 .

# 빌드된 이미지 확인
docker images | grep my-app

김개발 씨가 Dockerfile이 있는 프로젝트 폴더에서 터미널을 열었습니다. 박시니어 씨가 화면을 함께 보며 설명을 시작했습니다.

"자, 가장 기본적인 빌드 명령어를 입력해볼게요." docker build -t my-app:1.0 . 이 명령어에서 -t는 tag의 약자입니다.

이미지에 이름을 붙이는 역할을 합니다. my-app:1.0에서 콜론 앞은 이미지 이름이고, 뒤는 태그입니다.

태그는 보통 버전을 나타내는 데 사용합니다. 1.0, 2.0, latest 같은 식으로요.

마지막의 **점(.)**이 매우 중요합니다. 이것은 빌드 컨텍스트를 지정합니다.

"빌드 컨텍스트요?" 김개발 씨가 물었습니다. 박시니어 씨가 설명했습니다.

"Docker 빌드는 독특한 구조로 작동해요. docker build 명령을 실행하면 지정한 경로의 모든 파일이 Docker 데몬으로 전송돼요.

이 파일들이 빌드 컨텍스트예요." Dockerfile의 COPY 명령어는 이 빌드 컨텍스트 내의 파일만 복사할 수 있습니다. 상위 디렉토리나 다른 경로의 파일은 복사할 수 없습니다.

따라서 보통 Dockerfile은 프로젝트 루트에 위치하고, 빌드 컨텍스트도 프로젝트 루트를 지정합니다. 여기서 주의할 점이 있습니다.

node_modules 폴더나 .git 폴더처럼 크지만 이미지에 필요 없는 파일들도 전송됩니다. 이러면 빌드가 느려집니다.

"그래서 .dockerignore 파일이 필요해요." 박시니어 씨가 말했습니다. .dockerignore 파일은 .gitignore와 비슷합니다.

빌드 컨텍스트에서 제외할 파일 패턴을 작성합니다. node_modules, .git, *.log 같은 것들을 제외하면 빌드 속도가 크게 향상됩니다.

빌드를 실행하면 각 명령어가 순서대로 실행됩니다. 각 단계마다 레이어가 생성되고, 성공하면 다음 단계로 넘어갑니다.

중간에 실패하면 그 단계에서 멈추고 오류 메시지를 출력합니다. 실무에서 자주 사용하는 옵션들이 있습니다.

--build-arg는 빌드 시점에 변수를 전달합니다. Dockerfile에서 ARG로 선언한 변수에 값을 주입할 수 있습니다.

예를 들어 개발용과 운영용 빌드를 구분할 때 유용합니다. --no-cache 옵션은 캐시를 무시하고 처음부터 다시 빌드합니다.

베이스 이미지가 업데이트되었거나 캐시 문제가 의심될 때 사용합니다. 단, 빌드 시간이 길어지므로 꼭 필요할 때만 사용하세요.

빌드가 완료되면 docker images 명령어로 생성된 이미지를 확인할 수 있습니다. 이미지 ID, 생성 시간, 크기가 표시됩니다.

김개발 씨가 빌드 명령어를 실행했습니다. 각 단계가 차례로 진행되고, 마침내 "Successfully built"와 함께 이미지 ID가 출력되었습니다.

첫 번째 이미지 빌드에 성공한 것입니다.

실전 팁

💡 - .dockerignore 파일을 만들어 node_modules, .git, 로그 파일 등을 제외하세요

  • 태그를 생략하면 자동으로 latest가 붙지만, 명시적으로 버전을 지정하는 것이 좋습니다

5. docker run으로 컨테이너 실행

드디어 이미지가 완성되었습니다. 김개발 씨는 설레는 마음으로 이 이미지를 실행해보려 합니다.

docker run 명령어를 입력하면 될 것 같은데, 다양한 옵션들이 어떤 역할을 하는지 궁금해졌습니다.

docker run 명령어는 이미지로부터 컨테이너를 생성하고 실행합니다. -d 옵션으로 백그라운드 실행, --name으로 컨테이너 이름 지정, -p로 포트 매핑, -v로 볼륨 마운트를 할 수 있습니다.

컨테이너는 격리된 환경에서 실행되므로 외부와 통신하려면 명시적으로 설정해야 합니다.

다음 코드를 살펴봅시다.

# 기본 실행 (포그라운드, Ctrl+C로 종료)
docker run my-app:1.0

# 백그라운드 실행 (-d: detached)
docker run -d --name my-running-app my-app:1.0

# 포트 매핑과 함께 실행
docker run -d -p 3000:3000 --name web-server my-app:1.0

# 환경변수 전달
docker run -d -e NODE_ENV=production -e DB_HOST=localhost my-app:1.0

# 컨테이너 내부에 접속 (대화형 모드)
docker run -it my-app:1.0 /bin/sh

# 실행 후 자동 삭제 (테스트용)
docker run --rm my-app:1.0

이미지 빌드를 마친 김개발 씨가 첫 컨테이너를 실행하려고 합니다. 박시니어 씨가 옆에서 다양한 옵션을 설명해주기로 했습니다.

"가장 간단한 실행은 docker run 이미지명이에요." 박시니어 씨가 말했습니다. 이렇게 실행하면 컨테이너가 포그라운드에서 실행됩니다.

터미널이 컨테이너의 출력에 붙어있어서 로그를 바로 볼 수 있지만, 터미널을 닫으면 컨테이너도 종료됩니다. 개발 중에 로그를 보면서 테스트할 때 유용합니다.

실제 서비스에서는 -d 옵션을 사용합니다. detached의 약자로, 컨테이너를 백그라운드에서 실행합니다.

터미널을 닫아도 컨테이너는 계속 돌아갑니다. --name 옵션으로 컨테이너에 이름을 붙일 수 있습니다.

이름을 지정하지 않으면 Docker가 임의의 이름을 생성합니다. 하지만 명시적인 이름이 있어야 나중에 관리하기 편합니다.

docker stop web-server처럼 이름으로 컨테이너를 제어할 수 있습니다. "그런데 컨테이너 안에서 웹 서버가 3000번 포트로 돌고 있는데, 브라우저에서 접속이 안 돼요." 김개발 씨가 당황했습니다.

박시니어 씨가 고개를 끄덕였습니다. "컨테이너는 격리된 환경이에요.

포트 매핑을 해줘야 외부에서 접근할 수 있어요." -p 옵션이 바로 그 역할을 합니다. -p 3000:3000의 의미는 "호스트의 3000번 포트를 컨테이너의 3000번 포트에 연결하라"는 것입니다.

콜론 앞이 호스트 포트, 뒤가 컨테이너 포트입니다. 호스트 포트는 다르게 지정할 수도 있습니다.

-p 8080:3000으로 하면 브라우저에서 8080번으로 접속합니다. -e 옵션은 환경변수를 전달합니다.

데이터베이스 접속 정보나 실행 모드를 컨테이너에 주입할 때 사용합니다. 보안상 민감한 정보는 환경변수로 전달하는 것이 좋습니다.

코드에 하드코딩하면 이미지에 남아버리니까요. 디버깅을 위해 컨테이너 내부에 접속해야 할 때도 있습니다.

-it 옵션은 대화형 터미널을 엽니다. -i는 interactive, -t는 tty의 약자입니다.

docker run -it my-app:1.0 /bin/sh를 실행하면 컨테이너 내부의 셸에 접속할 수 있습니다. --rm 옵션은 컨테이너가 종료되면 자동으로 삭제합니다.

일회성 테스트나 빌드 작업에 유용합니다. 종료된 컨테이너가 쌓이는 것을 방지할 수 있습니다.

실행 중인 컨테이너에 접속하려면 docker exec를 사용합니다. docker exec -it 컨테이너명 /bin/sh를 입력하면 실행 중인 컨테이너에 들어갈 수 있습니다.

컨테이너를 중지하려면 docker stop, 삭제하려면 docker rm을 사용합니다. docker stop my-running-app && docker rm my-running-app처럼 연결해서 실행할 수도 있습니다.

김개발 씨가 docker run -d -p 3000:3000 --name my-web my-app:1.0을 실행했습니다. 브라우저에서 localhost:3000에 접속하자 자신이 만든 애플리케이션이 정상적으로 표시되었습니다.

실전 팁

💡 - 프로덕션에서는 반드시 -d로 백그라운드 실행하고, --name으로 명확한 이름을 지정하세요

  • docker logs 컨테이너명으로 백그라운드 컨테이너의 로그를 확인할 수 있습니다

6. 포트 포워딩과 볼륨 마운트

김개발 씨의 웹 애플리케이션이 잘 돌아가고 있습니다. 그런데 두 가지 고민이 생겼습니다.

첫째, 컨테이너를 재시작하면 데이터베이스에 저장한 데이터가 사라집니다. 둘째, 소스 코드를 수정할 때마다 이미지를 다시 빌드해야 하니 번거롭습니다.

어떻게 해결할 수 있을까요?

포트 포워딩은 호스트와 컨테이너 사이의 네트워크 연결을 만들어줍니다. 볼륨 마운트는 호스트의 디렉토리를 컨테이너 내부에 연결하여 데이터를 영구 보존하거나 실시간 파일 동기화를 가능하게 합니다.

이 두 기능은 개발과 운영 모두에서 필수적입니다.

다음 코드를 살펴봅시다.

# 포트 포워딩: 호스트 8080 -> 컨테이너 3000
docker run -d -p 8080:3000 my-app:1.0

# 여러 포트 매핑
docker run -d -p 3000:3000 -p 9229:9229 my-app:1.0

# 볼륨 마운트: 데이터 영구 보존 (named volume)
docker run -d -v mydata:/app/data my-app:1.0

# 바인드 마운트: 호스트 디렉토리 연결 (개발용)
docker run -d -v $(pwd)/src:/app/src my-app:1.0

# 읽기 전용 마운트
docker run -d -v $(pwd)/config:/app/config:ro my-app:1.0

# 포트 + 볼륨 조합 (실전 예시)
docker run -d \
  -p 3000:3000 \
  -v $(pwd)/src:/app/src \
  -v mydata:/app/data \
  --name dev-server my-app:1.0

김개발 씨는 Docker를 점점 능숙하게 다루게 되었습니다. 하지만 실무에서 몇 가지 문제를 마주했습니다.

박시니어 씨에게 조언을 구했습니다. "컨테이너를 껐다 켜면 데이터가 다 날아가요.

MySQL 컨테이너의 데이터베이스 내용이 사라져버렸어요." 박시니어 씨가 고개를 끄덕였습니다. "컨테이너의 파일 시스템은 임시예요.

컨테이너가 삭제되면 함께 사라지죠. 그래서 볼륨이 필요해요." 볼륨을 이해하려면 먼저 컨테이너의 특성을 알아야 합니다.

컨테이너는 이미지 위에 쓰기 가능한 레이어를 올린 것입니다. 이 레이어는 컨테이너와 생명주기를 함께합니다.

컨테이너를 삭제하면 레이어도 사라집니다. 볼륨은 이 문제를 해결합니다.

볼륨은 Docker가 관리하는 호스트의 특정 영역입니다. 컨테이너와 별개로 존재하므로 컨테이너를 삭제해도 볼륨은 남아있습니다.

docker run -d -v mydata:/app/data my-app:1.0 이 명령어에서 -v mydata:/app/data는 "mydata라는 이름의 볼륨을 컨테이너의 /app/data에 연결하라"는 의미입니다. 컨테이너가 /app/data에 파일을 쓰면 실제로는 볼륨에 저장됩니다.

나중에 같은 볼륨을 다른 컨테이너에 연결할 수도 있습니다. "그런데 개발할 때는 소스 코드를 수정하면 바로 반영되었으면 좋겠어요." 김개발 씨가 말했습니다.

박시니어 씨가 또 다른 방법을 알려주었습니다. "그럴 땐 바인드 마운트를 써요." 바인드 마운트는 호스트의 특정 디렉토리를 컨테이너에 직접 연결합니다.

docker run -d -v $(pwd)/src:/app/src my-app:1.0에서 $(pwd)/src는 현재 디렉토리의 src 폴더입니다. 호스트에서 src 폴더의 파일을 수정하면 컨테이너의 /app/src에도 즉시 반영됩니다.

개발 환경에서 이것은 매우 강력합니다. 코드를 수정할 때마다 이미지를 다시 빌드할 필요가 없습니다.

저장하면 바로 반영됩니다. nodemon이나 hot reload와 함께 사용하면 개발 생산성이 크게 향상됩니다.

볼륨과 바인드 마운트의 차이를 정리하면 이렇습니다. Named Volume은 Docker가 관리하며, 데이터 영구 보존에 적합합니다.

데이터베이스 파일 같은 것에 사용합니다. 바인드 마운트는 호스트 경로를 직접 지정하며, 개발 중 소스 코드 동기화에 적합합니다.

포트 포워딩도 다시 짚어봅시다. 컨테이너는 기본적으로 격리된 네트워크를 갖습니다.

외부에서 접근하려면 호스트의 포트와 컨테이너의 포트를 연결해야 합니다. -p 8080:3000은 호스트의 8080번 포트로 들어오는 요청을 컨테이너의 3000번 포트로 전달합니다.

호스트에서 여러 컨테이너를 실행할 때, 각각 다른 호스트 포트를 사용하면 충돌을 피할 수 있습니다. 실무에서는 포트 포워딩과 볼륨 마운트를 함께 사용합니다.

웹 서버 컨테이너는 포트를 열어 외부 접속을 받고, 볼륨으로 로그나 업로드 파일을 영구 저장합니다. 개발 환경에서는 바인드 마운트로 소스 코드를 동기화합니다.

김개발 씨가 모든 것을 조합해서 명령어를 실행했습니다. 이제 소스 코드를 수정하면 바로 반영되고, 데이터는 컨테이너를 재시작해도 유지됩니다.

Docker의 핵심 기능을 모두 익힌 것입니다.

실전 팁

💡 - 데이터베이스는 반드시 Named Volume을 사용하여 데이터를 보존하세요

  • 개발 환경에서는 바인드 마운트로 소스 코드를 연결하면 빌드 없이 실시간 반영됩니다
  • :ro 옵션을 붙이면 읽기 전용으로 마운트되어 실수로 파일을 수정하는 것을 방지합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Docker#Container#Dockerfile#Image#DevOps#Docker,Container

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

쿠버네티스 아키텍처 완벽 가이드

초급 개발자를 위한 쿠버네티스 아키텍처 설명서입니다. 클러스터 구조부터 Control Plane, Worker Node, 파드, 네트워킹까지 실무 관점에서 쉽게 풀어냅니다. 점프 투 자바 스타일로 술술 읽히는 이북 형식으로 작성되었습니다.

EFK 스택 로깅 완벽 가이드

마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.

Spring Boot 상품 서비스 구축 완벽 가이드

실무 RESTful API 설계부터 테스트, 배포까지 Spring Boot로 상품 서비스를 만드는 전 과정을 다룹니다. JPA 엔티티 설계, OpenAPI 문서화, Docker Compose 배포 전략을 초급 개발자도 쉽게 따라할 수 있도록 스토리텔링으로 풀어냅니다.

Docker로 컨테이너화 완벽 가이드

Spring Boot 애플리케이션을 Docker로 컨테이너화하는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 중심으로 설명합니다. Dockerfile 작성부터 멀티스테이지 빌드, 이미지 최적화, Spring Boot의 Buildpacks까지 다룹니다.

컨테이너 앱 CI/CD 구축 완벽 가이드

AWS ECS와 CodePipeline을 활용하여 컨테이너 기반 애플리케이션의 자동 배포 파이프라인을 구축하는 방법을 알아봅니다. 블루/그린 배포 전략과 모니터링 설정까지 단계별로 설명합니다.