[Docker 시리즈 2편] Dockerfile로 이미지와 컨테이너를 잇는 법

English version

1편에서 Docker가 개발 도구를 넘어 인프라 이야기로 이어진다는 흐름을 살펴봤습니다. 그 다리 역할을 하는 것이 바로 Dockerfile입니다. 이미지를 어떻게 만들고, 그 이미지가 어떻게 컨테이너로 실행되는지를 이해하면 "환경을 코드로 고정한다"는 말이 무엇인지 명확해집니다. 이번 글은 처음 Dockerfile을 작성해 보고, 이미지와 컨테이너의 차이를 손으로 체험할 수 있는 실습 위주로 정리했습니다.

이 글의 흐름

  1. Dockerfile이 맡는 역할 정리
  2. 기본 문법과 자주 쓰는 명령
  3. 레이어와 캐시 이해하기
  4. 첫 실습: 나만의 웹 서버 이미지 만들기
  5. 이미지 → 컨테이너 → 데이터 흐름 실습
  6. 다음 단계로 이어지는 학습 포인트

이번 글에서 새로 나오는 용어

  1. 레이어(Layer): Docker 이미지가 쌓이는 단위로, 명령마다 캐시와 재사용 여부가 결정됩니다.
  2. 베이스 이미지(Base Image): 새로운 이미지를 만들 때 시작점이 되는 기존 이미지입니다.
  3. 빌드 컨텍스트(Build Context): Docker가 이미지에 복사할 수 있도록 준비하는 폴더 범위입니다.
  4. 엔트리포인트(ENTRYPOINT): 컨테이너가 실행될 때 반드시 실행될 기본 명령입니다.
  5. .dockerignore: 빌드에 필요 없는 파일을 컨텍스트에서 제외하는 설정 파일입니다.

읽기 카드

  • 예상 소요 시간: 18분
  • 사전 준비: 명령형 리눅스 셸, Git으로 프로젝트 내려받을 수 있는 환경
  • 읽고 나면: Dockerfile 한 장으로 이미지와 컨테이너가 이어지는 과정을 단계별로 설명할 수 있습니다.

Dockerfile이 맡는 역할 정리

Dockerfile은 "어떤 환경을 어떤 순서로 만들 것인지"를 코드로 적어 두는 레시피입니다. 베이스 이미지 선택, 필요한 패키지 설치, 소스 코드 복사, 실행 명령 정의까지 한 파일에 담습니다. 덕분에 누가 빌드하든 같은 이미지가 나오고, 그 이미지는 어디서든 같은 컨테이너로 실행됩니다.

한 번 작성된 Dockerfile은 다음과 같은 장점을 줍니다.

  1. 환경 재현성: 같은 Dockerfile을 빌드하면 같은 이미지가 만들어집니다.
  2. 버전 관리: Git으로 Dockerfile을 관리하면 환경 변경 이력이 기록됩니다.
  3. 학습 효율: 어떤 명령이 어떤 역할을 하는지 한눈에 볼 수 있어 초보자도 흐름을 따라가기 쉽습니다.

기본 문법과 자주 쓰는 명령

가장 단순한 Dockerfile은 아래 형태로 시작합니다.

FROM ubuntu:24.04
RUN apt update && apt install -y curl
COPY . /app
CMD ["/app/run.sh"]
  • FROM: 시작점을 정합니다. ubuntu, alpine, node, nginx 등 공식 이미지를 주로 사용합니다.
  • RUN: 셸 명령을 실행하고 결과를 다음 레이어에 남깁니다.
  • COPY / ADD: 로컬 파일을 이미지 안으로 복사합니다.
  • CMD / ENTRYPOINT: 컨테이너가 실행될 때 기본으로 동작할 명령을 지정합니다.

이 외에도 자주 쓰는 명령이 있습니다.

  • WORKDIR /app: 이후 명령이 실행될 기본 디렉터리를 바꿉니다.
  • ENV NODE_ENV=production: 환경 변수를 이미지 레벨로 설정합니다.
  • EXPOSE 8080: 컨테이너가 어떤 포트를 사용한다고 알려 주는 메타데이터입니다. 실제로 외부에 포트를 열려면 docker run -p나 Compose의 ports 설정이 따로 필요합니다.

초보자가 많이 헷갈리는 부분이 바로 여기입니다. EXPOSE만 적는다고 브라우저에서 바로 접속할 수 있는 것은 아닙니다.

레이어와 캐시 이해하기

Docker는 명령이 실행될 때마다 스냅샷을 찍어 "레이어"라는 형태로 저장합니다. 그래서 Dockerfile을 조금만 잘 설계해도 빌드 시간을 크게 단축할 수 있습니다.

레이어 캐시를 잘 쓰는 기본 규칙은 다음과 같습니다.

  1. 자주 바뀌지 않는 단계는 위쪽에 둡니다. 예: 패키지 설치, 시스템 업데이트.
  2. 코드 복사는 아래쪽에 둡니다. 그래야 코드만 바뀌었을 때 앞선 단계 캐시를 재사용할 수 있습니다.
  3. 멀티 스테이지 빌드를 활용할 준비를 합니다. 빌드 도구와 실행 도구가 분리될수록 이미지가 가벼워집니다.

예를 들어 package.json만 먼저 복사하고 npm install을 실행한 뒤, 소스 코드는 나중에 복사하면 코드 한 줄이 바뀌어도 의존성 설치 캐시는 그대로 재사용하기 쉽습니다.

캐시 확인 실험

docker build -t cache-lab:v1 .
docker build -t cache-lab:v2 .

두 번째 빌드에서는 변경된 명령만 다시 실행되는 것을 로그에서 확인할 수 있습니다. 초보자라도 --progress=plain 옵션을 붙이면 어떤 레이어가 캐시를 썼는지 더 자세히 볼 수 있습니다.

첫 실습: 나만의 웹 서버 이미지 만들기

아래 미니 프로젝트로 Dockerfile 감각을 기릅니다.

  1. 폴더 만들기
mkdir -p docker-lab/app && cd docker-lab
cat <<'EOF' > app/index.html
<!doctype html>
<h1>안녕하세요 Docker!</h1>
<p>첫 컨테이너입니다.</p>
EOF
  1. Dockerfile 작성
# Dockerfile
FROM nginx:alpine
COPY ./app /usr/share/nginx/html
  1. 빌드하고 실행
docker build -t my-nginx-lab .
docker run -d -p 8080:80 --name lab my-nginx-lab
curl http://localhost:8080
docker stop lab && docker rm lab

짧은 Dockerfile 한 장으로도 나만의 이미지를 만들 수 있고, 그 이미지를 컨테이너로 실행하면 즉시 웹 서버가 됩니다. COPY 명령이 HTML 파일을 이미지 안에 포함시켰다는 사실을 체감해 보세요.

이때 현재 폴더 전체가 빌드 컨텍스트로 넘어간다는 점도 기억해 두면 좋습니다. 그래서 실무에서는 .dockerignorenode_modules, .git, 로그 파일 같은 불필요한 항목을 넣어 빌드 속도와 보안을 함께 관리합니다.

이미지 → 컨테이너 → 데이터 흐름 실습

이미지와 컨테이너의 차이를 헷갈린다면 아래 두 가지 실습을 해 보세요.

1. 이미지로 여러 컨테이너 띄우기

docker run -d --name web-a -p 8081:80 my-nginx-lab
docker run -d --name web-b -p 8082:80 my-nginx-lab

같은 이미지를 사용해도 서로 다른 컨테이너가 생성되고 독립적으로 동작합니다. 하나를 삭제해도 다른 컨테이너는 영향을 받지 않습니다.

2. 데이터 볼륨 붙여 보기

docker volume create html-data
docker run -d --name web-volume \
  -v html-data:/usr/share/nginx/html \
  -p 8083:80 nginx:alpine

docker exec -it web-volume sh -c "echo 'Volume Test' > /usr/share/nginx/html/index.html"
curl http://localhost:8083

docker rm -f web-volume
docker run -d --name web-volume \
  -v html-data:/usr/share/nginx/html \
  -p 8083:80 nginx:alpine
curl http://localhost:8083

컨테이너를 지웠다가 다시 만들어도 볼륨에 저장된 index.html은 그대로 남아 있다는 사실을 확인할 수 있습니다. 이 경험이 있어야 이후 데이터베이스, 캐시, 로그를 안전하게 다룰 수 있습니다.

다음 단계로 이어지는 학습 포인트

이제 Dockerfile 기본기를 익혔으니, 다음 글에서는 "어떤 베이스 이미지를 골라야 하는가"를 고민하게 됩니다. 우분투처럼 익숙한 환경이 좋은지, 알파인처럼 가벼운 환경이 좋은지에 따라 실습 난이도와 배포 전략이 갈립니다. 3편에서는 두 베이스 이미지를 비교하고, 학생들이 직접 실험해 볼 체크리스트와 Compose 예시를 소개하겠습니다.

추가로 한 가지 더 기억해 둘 점은, Dockerfile 안에 API 키나 비밀번호 같은 비밀값을 직접 적지 않는다는 것입니다. 그런 값은 나중에 환경 변수나 비밀 관리 도구로 주입하는 편이 안전합니다.

💬 댓글

이 글에 대한 의견을 남겨주세요