Dockerfile이란
Dockerfile은 Docker Image를 빌드하기 위한 명령어들의 집합이다.
텍스트 파일 형식으로 작성하며,
어떤 베이스 이미지 위에서 어떤 패키지를 설치하고,
어떤 코드를 복사하고,
어떻게 실행할지를 순서대로 정의한다.
Dockerfile을 작성하고 docker build 명령어를 실행하면,
Docker는 이 파일을 위에서부터 한 줄씩 읽어 Image를 만든다.
docker build -t my-app:v1.0 .
# -t: 태그(이름) 지정
# .: 현재 디렉토리의 Dockerfile을 사용
Dockerfile 기본 명령어
간단한 Python 웹 앱 Dockerfile을 예시로 각 명령어를 살펴보자
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV APP_ENV=production
ENV PORT=8000
EXPOSE 8000
CMD ["python", "app.py"]
FROM
모든 Dockerfile의 첫 번째 명령어다.
베이스 이미지를 지정한다.
완전히 빈 상태에서 시작하려면 FROM scratch를 사용한다.
FROM python:3.11-slim # Python 3.11이 설치된 경량 이미지
FROM ubuntu:22.04 # Ubuntu 22.04
FROM node:18-alpine # Node.js 18, Alpine Linux 기반 (매우 가벼움)
WORKDIR
컨테이너 안에서 작업 디렉토리를 설정한다.
이후의 RUN, COPY, CMD 등의 명령어는 이 디렉토리를 기준으로 실행된다.
디렉토리가 없으면 자동으로 생성한다.
WORKDIR /app # 이후 명령어는 /app 디렉토리 기준으로 실행됨
COPY와 ADD
호스트(내 컴퓨터)의 파일이나 디렉토리를 컨테이너 안으로 복사한다.
COPY requirements.txt . # 현재 디렉토리의 requirements.txt를 WORKDIR(.)로 복사
COPY src/ /app/src/ # src 폴더 전체를 /app/src/로 복사
ADD는 COPY와 비슷하지만 추가 기능이 있다.
URL에서 파일을 직접 다운로드하거나, .tar 파일을 자동으로 압축 해제한다.
단순 파일 복사에는 COPY를 사용하는 것이 권장된다.
ADD는 동작이 명시적이지 않아 예측하기 어렵기 때문이다.
RUN
이미지 빌드 과정에서 명령어를 실행한다.
패키지 설치, 파일 생성 등 빌드 시점에 필요한 작업을 수행한다.
실행 결과는 새로운 레이어로 저장된다.
RUN apt-get update && apt-get install -y curl
RUN pip install flask
ENV
컨테이너 안에서 사용할 환경변수를 설정한다.
빌드 시점과 실행 시점 모두에서 유효하다.
ENV APP_ENV=production
ENV PORT=8000
EXPOSE
컨테이너가 사용할 포트를 문서화한다.
실제로 포트를 열어주는 기능은 없다.
이 이미지가 어떤 포트를 사용하는지 명시적으로 알려주는 역할이다.
실제 포트 연결은 docker run -p로 한다.
EXPOSE 8000
CMD vs ENTRYPOINT
Dockerfile에서 가장 헷갈리는 부분이다.
둘 다 컨테이너가 시작될 때 실행할 명령어를 정의하지만, 역할이 다르다.
CMD
컨테이너 실행 시 기본으로 실행할 명령어를 정의한다.
docker run 뒤에 명령어를 추가로 입력하면 CMD는 완전히 덮어씌워진다.
CMD ["python", "app.py"]
docker run my-app # python app.py 실행 (CMD 그대로 사용)
docker run my-app python other.py # python other.py 실행 (CMD 덮어씌움)
docker run my-app /bin/bash # /bin/bash 실행 (CMD 덮어씌움)
ENTRYPOINT
컨테이너의 고정 실행 명령어를 정의한다.
docker run 뒤에 추가 인자를 입력해도 ENTRYPOINT는 덮어씌워지지 않는다.
추가 입력은 ENTRYPOINT의 인자로 전달된다.
ENTRYPOINT ["python", "app.py"]
docker run my-app # python app.py 실행
docker run my-app --port=9000 # python app.py --port=9000 실행 (인자로 추가됨)
CMD + ENTRYPOINT 함께 쓰기 (권장 패턴)
둘을 함께 사용하면 ENTRYPOINT는 고정 명령, CMD는 기본 인자 역할을 한다.
CMD 부분은 docker run 실행 시 원하는 값으로 교체할 수 있다.
ENTRYPOINT ["python", "app.py"]
CMD ["--port=8000"] # 기본 인자
docker run my-app # python app.py --port=8000 (기본)
docker run my-app --port=9000 # python app.py --port=9000 (CMD 교체됨)
언제 어떤 걸 쓸까?
| 상황 | 사용 |
| 실행 명령 자체를 자유롭게 바꿀 수 있어야 할 때 | CMD |
| 컨테이너가 하나의 목적에 고정되어야 할 때 | ENTRYPOINT |
| 고정 명령에 인자만 유연하게 바꾸고 싶을 때 | ENTRYPOINT + CMD 조합 |
RUN 레이어 최적화
RUN 명령어는 실행될 때마다 새로운 레이어를 만든다.
레이어가 많아지면 이미지 크기가 커지고 빌드도 느려진다.
나쁜 예시
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN rm -rf /var/lib/apt/lists/*
RUN이 4번 실행되므로 레이어가 4개 생긴다.
더 큰 문제는, rm -rf로 캐시를 지워도
이미 앞의 레이어에 저장되어 있기 때문에 이미지 크기가 줄어들지 않는다.
좋은 예시
RUN apt-get update && \\
apt-get install -y curl git && \\
rm -rf /var/lib/apt/lists/*
&&와 \\(줄 연속)로 하나의 RUN 명령으로 합친다.
레이어가 1개로 줄고, 캐시 삭제도 같은 레이어 안에서 이루어지므로 실제로 이미지 크기가 줄어든다.
COPY 순서도 최적화 대상이다
레이어 캐싱은 위에서부터 순서대로 동작한다.
변경이 잦은 파일일수록 아래에 배치해야 캐시 효율이 높아진다.
# 나쁜 예시: 코드 변경 때마다 패키지도 다시 설치됨
COPY . .
RUN pip install -r requirements.txt
# 좋은 예시: requirements.txt가 바뀌지 않으면 pip install 캐시 재사용
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
.dockerignore
.dockerignore 파일은 이미지 빌드 시 복사하지 않을 파일이나 디렉토리를 지정한다. .gitignore와 동일한 형식이다.
docker build를 실행하면 Docker는 빌드 컨텍스트를 Docker Daemon으로 전송한다.
.dockerignore로 불필요한 파일을 제외하면 전송 속도가 빠르고, 이미지에 민감한 정보가 포함되는 것을 방지한다.
# .dockerignore 예시
# 개발 의존성 및 설정
node_modules/
__pycache__/
*.pyc
.env
.env.local
# 버전 관리
.git/
.gitignore
# 테스트 및 문서
tests/
docs/
*.md
# 빌드 산출물
dist/
build/
*.log
⚠️ .env 파일에는 API 키, DB 비밀번호 같은 민감한 정보가 담겨 있는 경우가 많다.
.dockerignore에 반드시 포함해야 한다.
Multi-stage Build
문제: 빌드 도구가 최종 이미지에 포함된다
Go, Java, TypeScript 같은 언어는 소스 코드를 컴파일해서 실행 파일을 만든다.
컴파일 과정에서는 컴파일러, 빌드 도구 등이 필요하다. 하지만 실행 시에는 이것들이 전혀 필요 없다.
일반적인 방식으로 Dockerfile을 작성하면 빌드 도구가 그대로 최종 이미지에 포함돼서 이미지 크기가 불필요하게 커진다.
해결책: Multi-stage Build
Multi-stage Build는 하나의 Dockerfile 안에 여러 개의 빌드 단계(stage) 를 정의한다.
앞 단계에서 빌드하고, 결과물만 뒤 단계로 복사한다. 빌드 도구는 최종 이미지에 포함되지 않는다.
# ─── Stage 1: 빌드 단계 (Builder) ───────────────────────────────
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci # 의존성 설치
COPY . .
RUN npm run build # TypeScript 컴파일 → dist/ 폴더 생성
# ─── Stage 2: 실행 단계 (Runner) ────────────────────────────────
FROM node:18-alpine AS runner
WORKDIR /app
# builder 단계에서 만들어진 결과물만 복사
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
# 운영에 필요한 패키지만 설치 (devDependencies 제외)
RUN npm ci --only=production
CMD ["node", "dist/index.js"]
AS builder로 단계에 이름을 붙이고, COPY --from=builder로 이전 단계의 파일을 가져온다.
Multi-stage Build의 효과
| 일반 빌드 | Multi-stage 빌드 | |
| 포함되는 것 | 소스 코드 + 빌드 도구 + 결과물 | 결과물만 |
| 이미지 크기 | 크다 | 작다 (수십~수백 MB 절감 가능) |
| 보안 | 빌드 도구, 소스 노출 가능 | 최소한의 파일만 포함 |
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 39 - Docker Image & Container (0) | 2026.04.06 |
|---|---|
| [멋사 클라우드 5기] Day 37 & 38 - Linux (3) (0) | 2026.03.25 |
| [멋사 클라우드 5기] Day 35 & 36 - Linux (2) (0) | 2026.03.25 |
| [멋사 클라우드 5기] Day 33 & 34 - Linux (1) (1) | 2026.03.25 |
| [멋사 클라우드 5기] Day 31 & 32 - OSI 7 Layers (1) | 2026.03.16 |
