Dockerfile & Image Build
Dockerfile 작성, 이미지 빌드, 레이어 구조
📚 시리즈 네비게이션
| 이전 | 현재 | 다음 |
|---|---|---|
| Docker Basics | Dockerfile & Image | Volume & Network |
→ 시리즈 목차
🎯 Dockerfile이란?
이미지를 만들기 위한 명령어 스크립트.
# Dockerfile 예시
FROM nginx:latest
COPY index.html /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]# 빌드
docker build -t my-nginx .
# 실행
docker run -d -p 8080:80 my-nginx🏗️ 이미지 레이어 구조
Docker 이미지는 레이어 스택으로 구성됨.
flowchart TB subgraph Layers["Image Layers"] L4["Layer 4: CMD (실행 명령)"] L3["Layer 3: COPY index.html"] L2["Layer 2: EXPOSE 80"] L1["Layer 1: FROM nginx:latest<br/>(베이스 이미지)"] L4 --> L3 --> L2 --> L1 end
특징:
- 각 명령어가 새 레이어 생성
- 레이어는 캐시됨 (변경 없으면 재사용)
- 읽기 전용 (컨테이너 실행 시 쓰기 레이어 추가)
📝 Dockerfile 명령어
FROM
베이스 이미지 지정. 반드시 첫 줄에.
# 공식 이미지
FROM ubuntu:22.04
FROM node:18-alpine
FROM python:3.11-slim
# scratch (빈 이미지, Go 바이너리 등)
FROM scratch이미지 선택 팁:
| 태그 | 설명 | 용량 |
|---|---|---|
latest | 최신 버전 | 큼 |
alpine | Alpine Linux 기반 | 작음 |
slim | 불필요한 패키지 제거 | 중간 |
bullseye | Debian 버전명 | 큼 |
WORKDIR
작업 디렉토리 설정. (없으면 생성)
WORKDIR /app
# 이후 명령어는 /app에서 실행
COPY . .
RUN npm installCOPY vs ADD
# COPY: 로컬 파일 복사 (권장)
COPY package.json .
COPY src/ /app/src/
# ADD: COPY + 추가 기능 (URL, 압축 해제)
ADD https://example.com/file.tar.gz /app/
ADD archive.tar.gz /app/ # 자동 압축 해제권장: 대부분
COPY사용.ADD는 압축 해제 필요할 때만.
RUN
빌드 시 명령어 실행.
# 셸 형식
RUN apt-get update && apt-get install -y nginx
# exec 형식
RUN ["apt-get", "update"]
# 여러 명령어 (레이어 최소화)
RUN apt-get update && \
apt-get install -y \
nginx \
vim \
curl && \
rm -rf /var/lib/apt/lists/*CMD vs ENTRYPOINT
| 항목 | CMD | ENTRYPOINT |
|---|---|---|
| 용도 | 기본 명령어 | 고정 명령어 |
| docker run 인자 | 덮어씀 | 인자로 추가됨 |
| 여러 개 선언 | 마지막만 유효 | 마지막만 유효 |
# CMD: 기본 명령어 (덮어쓰기 가능)
CMD ["nginx", "-g", "daemon off;"]
# docker run my-nginx → nginx -g daemon off;
# docker run my-nginx cat /etc/os → cat /etc/os (CMD 무시)# ENTRYPOINT: 고정 명령어
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
# docker run my-app → python app.py --port 8080
# docker run my-app --port 9000 → python app.py --port 9000조합 패턴:
# 실행 파일 고정, 인자만 변경 가능
ENTRYPOINT ["python", "app.py"]
CMD ["--help"]
# docker run my-app → python app.py --help
# docker run my-app --debug → python app.py --debugENV
환경 변수 설정.
ENV NODE_ENV=production
ENV APP_HOME=/app \
APP_PORT=3000
# 사용
WORKDIR $APP_HOME
EXPOSE $APP_PORTARG
빌드 시 인자. (이미지에 남지 않음)
ARG VERSION=latest
ARG BUILD_DATE
FROM node:${VERSION}
RUN echo "Build date: ${BUILD_DATE}"docker build --build-arg VERSION=18 --build-arg BUILD_DATE=$(date) .| 항목 | ARG | ENV |
|---|---|---|
| 사용 시점 | 빌드 시 | 빌드 + 런타임 |
| 이미지에 저장 | ❌ | ✅ |
| 컨테이너에서 접근 | ❌ | ✅ |
EXPOSE
문서화 용도의 포트 선언. (실제 포트 오픈 아님)
EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 53/udp실제 포트 매핑은
docker run -p로 해야 함.
USER
실행 사용자 변경.
# 사용자 생성
RUN useradd -r -u 1001 appuser
# 사용자 변경
USER appuser
# 이후 명령어는 appuser로 실행VOLUME
볼륨 마운트 포인트 선언.
VOLUME /data
VOLUME ["/data", "/logs"]HEALTHCHECK
컨테이너 상태 체크.
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1🎯 실습: Node.js 앱
프로젝트 구조
my-node-app/
├── Dockerfile
├── package.json
├── package-lock.json
└── src/
└── index.js
package.json
{
"name": "my-node-app",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"express": "^4.18.2"
}
}src/index.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello Docker!', timestamp: new Date() });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Dockerfile
# 베이스 이미지
FROM node:18-alpine
# 작업 디렉토리
WORKDIR /app
# 의존성 파일 먼저 복사 (캐시 활용)
COPY package*.json ./
# 의존성 설치
RUN npm ci --only=production
# 소스 복사
COPY src/ ./src/
# 포트 문서화
EXPOSE 3000
# 실행
CMD ["npm", "start"]빌드 및 실행
# 빌드
docker build -t my-node-app .
# 실행
docker run -d -p 3000:3000 --name app my-node-app
# 확인
curl http://localhost:3000
# 로그
docker logs app
# 정리
docker rm -f app🎯 실습: Python Flask 앱
Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 의존성 먼저
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 복사
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]requirements.txt
flask==3.0.0
app.py
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify(message='Hello Docker!', host=os.uname().nodename)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)⚡ 이미지 최적화
1. 레이어 최소화
# 나쁜 예 (레이어 3개)
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
# 좋은 예 (레이어 1개)
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*2. 캐시 활용
# 나쁜 예: 소스 변경 시 npm install 다시 실행
COPY . .
RUN npm install
# 좋은 예: package.json 변경 없으면 캐시 사용
COPY package*.json ./
RUN npm install
COPY . .3. 작은 베이스 이미지
# 큰 이미지 (~900MB)
FROM node:18
# 작은 이미지 (~170MB)
FROM node:18-alpine
# 더 작게 (~120MB)
FROM node:18-alpine
RUN npm ci --only=production4. 멀티스테이지 빌드
빌드 도구는 최종 이미지에 불필요함.
flowchart LR subgraph Build["빌드 스테이지"] B1["FROM node:18-alpine AS builder"] B2["npm ci"] B3["npm run build"] B1 --> B2 --> B3 end subgraph Run["실행 스테이지"] R1["FROM nginx:alpine"] R2["COPY --from=builder<br/>/app/dist"] R1 --> R2 end Build -->|"빌드 결과만 복사"| Run
# 빌드 스테이지
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 실행 스테이지 (빌드 결과만 복사)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Go 예시:
# 빌드
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o main .
# 실행 (scratch = 빈 이미지)
FROM scratch
COPY --from=builder /app/main /main
CMD ["/main"]5. .dockerignore
불필요한 파일 제외.
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
Dockerfile
.dockerignore
*.md
.env
.env.*
🏷️ 이미지 태깅 & 푸시
태깅
# 빌드 시 태그
docker build -t myapp:1.0.0 .
docker build -t myapp:latest .
# 기존 이미지에 태그 추가
docker tag myapp:1.0.0 myapp:stable
docker tag myapp:1.0.0 myregistry.com/myapp:1.0.0Docker Hub 푸시
# 로그인
docker login
# 태그 (username 포함)
docker tag myapp:1.0.0 username/myapp:1.0.0
# 푸시
docker push username/myapp:1.0.0프라이빗 레지스트리
# AWS ECR
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789.dkr.ecr.ap-northeast-2.amazonaws.com
docker tag myapp:1.0.0 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:1.0.0
docker push 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:1.0.0
# Harbor, GitLab Registry 등도 유사📋 Dockerfile 체크리스트
| 항목 | 확인 |
|---|---|
| 작은 베이스 이미지 사용 (alpine, slim) | ☐ |
| .dockerignore 설정 | ☐ |
| 의존성 파일 먼저 COPY (캐시 활용) | ☐ |
| RUN 명령어 합치기 (레이어 최소화) | ☐ |
| 불필요한 파일 삭제 (apt cache 등) | ☐ |
| 멀티스테이지 빌드 고려 | ☐ |
| non-root 사용자 사용 | ☐ |
| HEALTHCHECK 설정 | ☐ |