안녕하세요! 지난 포스팅에서는 Docker Official Image를 사용해서 컨테이너를 실행해보았는데요. 이미 만들어진 이미지를 사용하는 것도 좋지만, 결국 우리는 우리만의 애플리케이션을 도커에 담아 배포해야겠죠? 오늘은 Dockerfile을 작성해서 우리만의 커스텀 이미지를 만드는 방법을 자세히 알려드릴게요. 도커 초보자분들도 쉽게 따라오실 수 있도록 차근차근 설명해 드릴 테니 걱정하지 마세요! 🚀
1. Dockerfile, 이게 뭐죠? 🤔
먼저, Dockerfile이 무엇인지부터 알아볼까요?
Dockerfile은 한마디로 도커 이미지를 만드는 방법을 적어 놓은 설계도라고 생각하시면 됩니다. 이 파일 안에 어떤 운영체제를 기반으로 할지, 어떤 파일을 복사할지, 어떤 프로그램을 설치할지, 그리고 최종적으로 어떤 명령어를 실행할지 등 이미지 생성에 필요한 모든 지시사항을 담습니다. 도커는 이 Dockerfile을 읽어서 자동으로 이미지를 만들어주죠.

API가 있는 NodeJS 테스트 프로젝트를 만들어 주세요. 그리고 우리 프로젝트의 루트 폴더에 Dockerfile이라는 이름으로 파일을 만들어주세요. 이 이름은 도커가 파일을 인식하는 특별한 이름이니 꼭 지켜야 합니다. Visual Studio Code를 사용하신다면 Docker 확장 프로그램을 설치하시면 문법 하이라이팅 등 작성에 큰 도움을 받을 수 있습니다.
2. Dockerfile 작성하기: 한 줄 한 줄 따라가기 📝
자, 이제 본격적으로 Dockerfile을 작성해 봅시다. 간단한 Node.js API 서버 프로젝트가 준비되었다고 가정하고, 아래 예시 코드를 바탕으로 각 명령어의 의미를 하나씩 짚어볼게요.
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci
EXPOSE 3000
CMD ["node", "server.js"]
① FROM: 기본 베이스 이미지 선택하기
FROM node:18-alpine
FROM 명령어는 어떤 이미지를 기반으로 우리만의 이미지를 만들지를 지정합니다. 이미지를 처음부터 만드는 것은 매우 복잡한 일이므로, 보통은 이렇게 이미 만들어진 **베이스 이미지(Base Image)**를 활용합니다. 이론적으로는 도커 이미지를 맨 처음부터 만들 수도 있지만, 항상 어떤 종류의 운영체제나 필요한 도구 레이어를 포함하고 싶을 겁니다.
여기서는 Node.js 18 버전이 설치되어 있는 node:18-alpine 이미지를 베이스로 사용했습니다. alpine은 경량 리눅스 배포판으로, 이미지 크기를 줄이는 데 효과적이라 많이 사용됩니다.
FROM node라고만 입력하면 도커는 먼저 로컬 시스템에 node라는 이름의 이미지가 있는지 확인합니다. 만약 없다면, **도커 허브(Docker Hub)**에서 해당 이름의 이미지를 찾아 다운로드합니다. 이 이미지는 로컬에 한 번 다운로드되면 캐시 되므로, 다음부터는 도커 허브에서 다시 다운로드할 필요 없이 로컬 이미지를 사용하게 됩니다.
② WORKDIR & COPY: 작업 공간 설정과 파일 복사
WORKDIR과 COPY 명령어는 컨테이너 내부에 우리 코드를 배치하는 데 핵심적인 역할을 합니다.
WORKDIR /app
COPY . .
WORKDIR /app은 컨테이너 내부에 /app이라는 작업 디렉토리를 만들고, 이후의 모든 명령어(예: COPY, RUN, CMD)가 이 디렉토리 안에서 실행되도록 설정합니다.
COPY . . 명령어는 두 개의 경로를 지정합니다. 첫 번째 .은 Dockerfile이 있는 로컬 시스템의 경로를 의미하며, 도커파일을 제외한 모든 파일과 하위 폴더가 포함됩니다. 두 번째 .은 컨테이너 내부의 경로를 의미하는데, 앞서 WORKDIR로 /app을 설정했기 때문에 이는 곧 /app 폴더를 가리킵니다.
따라서 이 두 명령어를 조합하면 로컬 프로젝트 폴더의 모든 파일을 컨테이너의 /app 폴더로 복사하는 결과를 만들어 냅니다.

💡 잠깐! 왜 파일 복사가 필요할까요?
도커 컨테이너는 로컬 시스템과 완전히 격리된 독립적인 환경입니다. 컨테이너 내부에는 자체적인 파일 시스템이 존재하며, 이는 여러분의 로컬 파일 시스템과 완전히 분리되어 있습니다. 따라서 컨테이너 안에서 우리 코드를 실행하려면, 먼저 컨테이너 내부로 코드를 복사해 넣어야 합니다. 컨테이너는 환경(운영체제)과 코드(우리 프로젝트)를 모두 포함하고 있기 때문입니다.
③ RUN: 컨테이너 내부에서 명령어 실행하기
RUN npm ci
RUN 명령어는 이미지가 생성되는 과정에서 컨테이너 내부에서 실행할 셸 명령어를 지정합니다. 이 명령어는 이미지에 반영되어 영구적으로 남게 됩니다.
여기서는 npm ci 명령어를 사용했습니다. npm install과 npm ci 모두 의존성 패키지를 설치하는 명령어이지만, 도커 환경에서는 npm ci 사용이 권장됩니다.
- npm install: package.json 파일을 기반으로 의존성을 설치하며, package-lock.json 파일이 존재하지 않으면 새로 생성하거나 업데이트할 수 있습니다.
- npm ci (clean install): package-lock.json 파일을 기반으로 정확하게 의존성을 설치합니다. 이 파일의 내용과 일치하지 않는다면 설치를 중단합니다. 이는 여러 개발 환경이나 배포 환경에서 일관된 의존성을 보장하는 데 매우 중요합니다.
④ EXPOSE: 포트 노출하기
EXPOSE 3000
EXPOSE 명령어는 컨테이너가 특정 포트를 사용하고 있다는 것을 알려주는 역할을 합니다. 하지만 이 명령어만으로는 실제로 포트가 외부와 연결되지 않습니다.
도커 컨테이너는 기본적으로 외부와 격리된 자신만의 네트워크를 사용합니다. 컨테이너 내부에서 아무리 특정 포트(예: 3000번)로 서버를 실행해도, 우리 로컬 시스템은 해당 포트에 접근할 수 없습니다.
EXPOSE는 이 포트가 외부로 노출될 준비가 되었다는 일종의 신호이자 문서화 역할을 합니다. 실제 포트 연결(매핑)은 docker run 명령어를 실행할 때 -p 옵션을 사용해서 지정합니다. 예를 들어, docker run -p 80:3000처럼 지정하면 로컬 PC의 80번 포트로 들어온 요청을 컨테이너 내부의 3000번 포트로 전달하게 됩니다.
⑤ CMD: 컨테이너 실행 시 명령어 지정하기
CMD ["node", "server.js"]
CMD 명령어는 컨테이너가 시작될 때 실행할 기본 명령어를 지정합니다. RUN과 달리, 이 명령어는 이미지를 만들 때 실행되는 것이 아니라, 이미지를 바탕으로 컨테이너를 실행할 때 딱 한 번 실행됩니다. 만약 RUN node server.js를 사용했다면, 이는 이미지가 만들어지는 과정에서 서버를 실행하려 시도할 것이고 이는 우리가 원하는 바가 아닙니다.
Node.js 서버를 시작하기 위해 node server.js 명령어를 배열 형태로 작성했습니다. 이렇게 작성하면 도커가 이 명령어를 실행 가능한 형태로 바꿔줍니다. 만약 CMD를 지정하지 않으면, 베이스 이미지의 CMD가 실행되거나 오류가 발생할 수 있습니다.
💡 CMD와 RUN의 차이점, 다시 한번 짚고 넘어가기!
RUN: 이미지 생성 단계에서 실행. 여러 번 사용 가능. (ex: RUN apt-get update, RUN npm ci)
CMD: 컨테이너 시작 단계에서 실행. Dockerfile당 한 번만 사용 가능. (ex: CMD ["npm", "start"])
이번 포스팅에서는 Dockerfile을 작성해서 커스텀 이미지를 만드는 방법을 자세히 알아봤습니다. 다음 포스팅에서는 실제로 이 Dockerfile을 바탕으로 이미지를 빌드하고, 컨테이너를 실행하며 포트를 연결하는 방법을 다뤄보겠습니다.
궁금한 점이 있다면 언제든 댓글로 남겨주세요! 😊
'DevOps' 카테고리의 다른 글
| [Docker 6편] 컨테이너의 다양한 모드와 상호작용하기 🐳 (1) | 2025.08.19 |
|---|---|
| [Docker 5편] 컨테이너와 이미지 관리 명령어 모음 🐳 (2) | 2025.08.18 |
| [Docker 4편] 🐳 도커, 넌 대체 어떻게 빌드되는 거야? 이미지 레이어! (2) | 2025.08.18 |
| [Docker 3편] DockerFile로 도커 이미지 만들고 컨테이너 실행하기 (2) | 2025.08.17 |
| [Docker 1편] 내 컴퓨터에서는 잘 되는데? 개발자를 위한 도커(Docker) 이야기 (4) | 2025.08.15 |