프로그래밍/DevOps

[DevOps] Dockerfile 작성법 — 내 앱을 직접 이미지로 만들기

이슬먹는 개발자 2026. 7. 7. 14:11
728x90
반응형

지난 글에서 컨테이너가 뭔지, 이미지가 붕어빵 틀이라는 것까지 잡았다. 그때는 nginx나 hello-world처럼 남이 만들어둔 이미지를 가져다 썼다. 오늘은 한 걸음 더 나간다. 내 앱을 담은 이미지를 직접 만드는 법, 바로 Dockerfile이다.

이걸 할 줄 알아야 "내가 만든 프로그램을 어디서든 똑같이 돌린다"는 Docker의 진짜 목적에 도달한다.


Dockerfile = 이미지 만드는 레시피

Dockerfile은 "이 이미지를 이렇게 만들어라"라고 적어둔 설명서(레시피)다. 붕어빵 틀(이미지)을 찍어내기 위한 설계도라고 보면 된다.

베이스 환경은 뭘 쓸지, 내 코드는 어디에 복사할지, 어떤 명령으로 실행할지를 위에서 아래로 순서대로 적는다. 그러면 Docker가 그 순서대로 실행해서 이미지를 만들어준다.

흐름은 이렇다.

Dockerfile (레시피)  →  docker build  →  이미지  →  docker run  →  컨테이너

예제: 간단한 Node.js 앱을 이미지로

간단한 Node.js 웹 앱이 있다고 하자. 프로젝트 구조는 이렇다.

my-app/
├── package.json
├── app.js
└── Dockerfile   ← 이걸 만든다

이 앱을 이미지로 만드는 Dockerfile은 이렇게 생겼다.

# 1. 베이스 이미지 선택
FROM node:20

# 2. 컨테이너 안 작업 폴더 지정
WORKDIR /app

# 3. 의존성 파일 먼저 복사
COPY package*.json ./

# 4. 의존성 설치
RUN npm install

# 5. 나머지 소스코드 전체 복사
COPY . .

# 6. 앱이 사용할 포트 알려주기
EXPOSE 3000

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

한 줄씩 뜯어보자.


명령어 하나씩 뜯어보기

FROM node:20 — 베이스 이미지다. 맨땅부터 시작하는 게 아니라, Node 20이 이미 깔린 이미지 위에서 시작한다. 모든 Dockerfile은 FROM으로 시작한다.

WORKDIR /app — 컨테이너 안에서 작업할 디렉토리를 정한다. 이후 명령들은 다 이 폴더 기준으로 돌아간다. 없으면 자동으로 만들어준다.

COPY package*.json ./ — 내 컴퓨터의 package.json을 컨테이너 안으로 복사한다. 소스 전체가 아니라 의존성 파일만 먼저 복사하는 게 포인트다. (이유는 아래 캐시 설명에서.)

RUN npm install — 이미지를 만드는 시점에 실행되는 명령이다. 여기서 의존성을 설치한다. RUN은 이미지 빌드 중에 실행된다는 걸 기억하자.

COPY . . — 이제 나머지 소스코드 전체를 복사한다.

EXPOSE 3000 — 이 컨테이너가 3000번 포트를 쓴다고 알려주는 문서 역할이다. (실제 연결은 run 할 때 -p로 한다.)

CMD ["node", "app.js"] — 컨테이너가 시작될 때 실행할 명령이다. RUN이 빌드 시점이라면 CMD는 실행 시점이다. 이 차이가 처음엔 헷갈리니 꼭 구분하자.


빌드하고 실행하기

Dockerfile을 다 썼으면 이미지를 만든다(build).

docker build -t my-app .
  • -t my-app : 이미지에 my-app이라는 이름(태그)을 붙인다.
  • . : Dockerfile이 있는 현재 폴더를 빌드 대상으로 삼는다.

만들어진 이미지를 확인하고,

docker images

컨테이너로 실행한다.

docker run -d -p 3000:3000 my-app

이제 localhost:3000으로 들어가면 내가 만든 앱이 컨테이너 안에서 돌아간다. 내 앱이 드디어 "어디서든 똑같이 도는" 이미지가 된 거다.


RUN vs CMD — 헷갈리는 포인트 정리

입문자가 가장 많이 헷갈리는 게 RUNCMD다. 표로 정리한다.

구분 실행 시점 용도 예시
RUN 이미지 빌드 설치, 준비 작업 RUN npm install
CMD 컨테이너 실행 앱 시작 명령 CMD ["node", "app.js"]

RUN은 붕어빵 틀을 만들 때(재료 넣고 반죽) 하는 일, CMD는 그 틀로 붕어빵을 구울 때(실제로 굽기) 하는 일이라고 생각하면 편하다.


왜 package.json을 먼저 복사했을까? (레이어 캐시)

아까 소스 전체(COPY . .)보다 package.json을 먼저 복사하고 npm install을 했다. 이건 실수가 아니라 빌드 캐시 때문이다.

Docker는 Dockerfile의 각 줄을 레이어로 만들어 캐싱한다. 바뀌지 않은 레이어는 다시 빌드하지 않고 재사용한다. 만약 소스코드만 바꾸고 의존성은 그대로일 때,

  • package.json을 먼저 복사해두면 → npm install 레이어는 캐시가 살아있어서 재설치 안 함 (빠름)
  • 소스랑 같이 복사했으면 → 코드 한 줄만 바꿔도 캐시가 깨져서 npm install매번 다시 (느림)

그래서 "잘 안 바뀌는 것(의존성)을 위에, 자주 바뀌는 것(소스)을 아래에" 두는 게 Dockerfile 작성의 기본 요령이다.


정리

오늘은 내 앱을 직접 이미지로 만드는 법을 배웠다.

  • Dockerfile은 이미지를 만드는 레시피다.
  • FROM(베이스) → WORKDIRCOPYRUN(빌드 시점 작업) → CMD(실행 시점 명령) 순서가 기본 뼈대다.
  • docker build로 이미지를 만들고, docker run으로 컨테이너를 띄운다.
  • RUN은 빌드 시점, CMD는 실행 시점이라는 차이를 꼭 기억하자.
  • 잘 안 바뀌는 걸 위에 두면 레이어 캐시 덕에 빌드가 빨라진다.

이제 내 앱 하나를 이미지로 만들 수 있게 됐다. 그런데 실무 앱은 보통 혼자 안 돈다. 웹 서버 + 데이터베이스 + 캐시(Redis)처럼 여러 컨테이너가 같이 떠야 한다. 다음 글에서는 이런 여러 컨테이너를 한 번에 관리하는 Docker Compose를 다뤄보자.

728x90
반응형