도커 이미지에 여러 가지 기능을 넣다 보면 이미지 무거워질 수 있다. 이런 경우 가볍게 이미지를 만들 수 있도록 최적화 시키는 것이 필요한데, 그것을 할 수 있는 방법으로 Multi stage build 라는 방법이 있다.
원래 Dockerfile을 build 할 때 FROM으로 시작하는데, FROM이 2개 이상 나오고 이전에 만들어 놓은 환경에서 필요한 것만 가져다 쓰면 Multi stage build로 볼 수 있다.
이 글에서는 빌더와 러너(fastapi)로 나누어 실행해 보겠다. 구조는 대충 아래와 같다.
fastapi-docker-demo/
├── Dockerfile
├── .dockerignore
├── main.py
└── requirements.txt
해당 파일은 fastAPI로 API를 만든다고 가정하고 health check 기능을 담은 코드를 사용한다.
from fastapi import FastAPI
app = FastAPI()
@app.get('/health')
def health():
return {'status': 'ok'}
fastAPI를 사용하니 자연스럽게 의존성 패키지로 fastAPI와 uvicorn을 설치해야 한다.
fastapi==0.106.0
uvicorn[standard]==0.24.0
# 1단계: 빌드 스테이지 (필요시)
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 2단계: 실행 스테이지
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
여기서는 기본적으로 빌드와 러너로 나누어 작업을 하는데, 나누어야 하는 이유는 대략 다음과 같다.
경량화
보안 강화
유지 보수 용이
- 당연히 스테이지를 나누어 사용하니까.
해당 코드에서 중요하게 주목해야 하는 것은 두가지로 FROM으로 나뉜 두개의 스테이지와 COPY를 통해 빌더에 있는 /root/.local 디렉토리를 러너 실행하는 컨테이너의 /root/.local에 복사하는 부분이다.
빌더에서 설치한 것들을 가져와서 여러번 수행할 필요가 없어져 이미지의 무게가 최적화 될 수 있다.
__pycache__/
*.pyc
.git
.env
불필요한 확장자의 파일을 사용하지 않게 되어 코드가 이미지 용량 최적화에 도움이 됨
$ docker build -t fastapi-multi .
docker run -d --rm --name myapp -p 8000:8000 fastapi-multi
-d는 백그라운드에서 실행하라는 의미, --rm 해당 컨테이너를 종료하면 지우라는 의미

위의 예시 코드는 잘 동작하면 이렇게 결과가 나옴