1 왜 Docker인가
“내 컴퓨터에서는 되는데 서버에서는 안 된다”는 문제가 있다. Python 버전, 라이브러리 버전, 시스템 패키지, 환경 변수 등 환경 차이로 발생한다.
Docker는 애플리케이션을 실행 환경째 패키징한다. Python, 라이브러리, 설정 파일까지 모두 하나의 이미지로 묶으면 어디서든 동일하게 실행된다.
| 문제 | Docker 이전 | Docker 이후 |
|---|---|---|
| Python 버전 충돌 | 서버마다 수동 설치 | 이미지에 Python 3.12 고정 |
| 라이브러리 버전 | pip install 결과가 서버마다 다름 |
requirements.txt로 정확히 재현 |
| 시스템 패키지 | 운영팀에 요청 | Dockerfile에 명시 |
| 환경 변수 | 서버마다 수동 설정 | .env 파일 또는 compose로 관리 |
2 핵심 개념
2.1 이미지 (Image)
이미지는 컨테이너를 만들기 위한 읽기 전용 템플릿이다. 프로그램 코드, 런타임, 라이브러리, 설정 파일이 모두 포함된다.
클래스(이미지)와 인스턴스(컨테이너)의 관계와 유사하다:
2.2 컨테이너 (Container)
컨테이너는 이미지를 실행한 격리된 프로세스이다. 자체 파일 시스템, 네트워크, 프로세스 공간을 가진다.
2.3 컨테이너 vs 가상머신
| 기준 | 컨테이너 | 가상머신 (VM) |
|---|---|---|
| 격리 수준 | 프로세스 수준 | 하드웨어 수준 |
| OS | 호스트 커널 공유 | 독자 OS (게스트 OS) |
| 시작 시간 | 초 단위 | 분 단위 |
| 크기 | 수십~수백 MB | 수 GB |
| 리소스 오버헤드 | 낮음 | 높음 |
| 사용 사례 | 앱 패키징, 마이크로서비스 | 완전한 OS 격리 |
컨테이너는 호스트 OS의 커널을 공유하므로 가볍고 빠르다. AI Agent처럼 동일한 서비스를 여러 인스턴스로 실행할 때 적합하다.
3 Dockerfile
Dockerfile은 이미지를 빌드하기 위한 명령어 스크립트이다.
3.1 기본 구조
# 베이스 이미지 — 시작점
FROM python:3.12-slim
# 작업 디렉토리 설정
WORKDIR /app
# 의존성 파일 복사 및 설치 (캐시 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드 복사
COPY src/ ./src/
# 환경 변수
ENV ENVIRONMENT=production
# 포트 노출 (문서화 목적)
EXPOSE 8000
# 실행 명령
CMD ["uvicorn", "src.services.api.main:app", "--host", "0.0.0.0", "--port", "8000"]3.2 주요 명령어
| 명령어 | 역할 | 예시 |
|---|---|---|
FROM |
베이스 이미지 지정 | FROM python:3.12-slim |
WORKDIR |
작업 디렉토리 설정 | WORKDIR /app |
COPY |
파일/디렉토리 복사 | COPY src/ ./src/ |
RUN |
빌드 시 명령 실행 | RUN pip install -r requirements.txt |
ENV |
환경 변수 설정 | ENV LOG_LEVEL=INFO |
EXPOSE |
포트 문서화 | EXPOSE 8000 |
CMD |
컨테이너 시작 명령 | CMD ["uvicorn", "..."] |
ENTRYPOINT |
고정 시작 명령 | ENTRYPOINT ["python"] |
3.3 빌드와 실행
3.4 레이어 캐시 최적화
Dockerfile의 각 명령어는 레이어를 생성한다. 변경되지 않은 레이어는 캐시에서 재사용되므로, 자주 변경되는 파일일수록 아래에 배치한다.
# 잘못된 순서 — 코드가 바뀔 때마다 pip install도 다시 실행
COPY . .
RUN pip install -r requirements.txt
# 올바른 순서 — requirements.txt가 변경될 때만 pip install 재실행
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/ # 코드 변경은 여기만 영향의존성 설치(pip install)는 시간이 오래 걸리므로, 소스 코드 변경 시에는 캐시를 활용하여 건너뛸 수 있게 배치하는 것이 중요하다.
4 멀티스테이지 빌드
빌드에 필요한 도구(Node.js, 컴파일러 등)를 최종 이미지에 포함하지 않아 이미지 크기를 줄인다.
4.1 React + FastAPI 멀티스테이지
AI Agent 플랫폼처럼 프론트엔드(React)와 백엔드(FastAPI)를 하나의 이미지로 빌드하는 예이다.
# Stage 1: React 빌드
FROM node:20-slim AS frontend
WORKDIR /app/frontend
COPY frontend/package*.json .
RUN npm ci
COPY frontend/ .
RUN npm run build
# 결과: /app/frontend/dist/ 에 정적 파일 생성
# Stage 2: Python 런타임
FROM python:3.12-slim AS runtime
WORKDIR /app
# 시스템 패키지
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Python 의존성
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드
COPY src/ ./src/
COPY data/configs/ ./data/configs/
# React 빌드 결과를 정적 파일 디렉토리로 복사
COPY --from=frontend /app/frontend/dist ./static
# 실행
ENV ENVIRONMENT=production
EXPOSE 8000
CMD ["uvicorn", "src.services.api.main:app", "--host", "0.0.0.0", "--port", "8000"]COPY --from=frontend으로 Stage 1의 빌드 결과만 가져온다. 최종 이미지에는 Node.js가 포함되지 않아 크기가 크게 줄어든다.
| 방식 | 이미지 크기 |
|---|---|
| 단일 스테이지 (Node + Python) | ~1.5 GB |
| 멀티스테이지 | ~500 MB |
5 Docker Compose
여러 컨테이너를 함께 실행할 때 사용한다.
# docker-compose.yml
services:
api:
build: .
ports:
- "8000:8000"
environment:
- AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_KEY=${AZURE_OPENAI_KEY}
- LLM_PROVIDER=azure
- LOG_LEVEL=INFO
volumes:
- ./data/docs:/app/data/docs # 지식베이스 문서 마운트
- ./data/experiments:/app/data/experiments
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped# 모든 서비스 시작
docker compose up -d
# 로그 확인
docker compose logs -f api
# 서비스 중지 및 삭제
docker compose down
# 이미지 재빌드 후 시작
docker compose up -d --build5.1 환경 변수 관리
# docker-compose.yml
services:
api:
env_file:
- .env # 로컬 환경 변수 파일
environment:
- ENVIRONMENT=production # 직접 지정 (env_file보다 우선)# .env
AZURE_OPENAI_ENDPOINT=https://my-resource.openai.azure.com/
AZURE_OPENAI_KEY=sk-xxx
LLM_PROVIDER=azure.env 파일은 .gitignore에 추가하여 git에 올리지 않는다. 민감한 정보(API 키 등)가 포함되기 때문이다.
6 .dockerignore
빌드 컨텍스트에서 불필요한 파일을 제외한다. .gitignore와 같은 역할이다.
# .dockerignore
.git
.venv
__pycache__
node_modules
*.pyc
.env
.env.local
data/docs/*.pdf
*.log
.dockerignore가 없으면 docker build 시 .venv, node_modules 등 수 GB의 파일이 빌드 컨텍스트에 포함되어 빌드 시간이 크게 늘어난다.
7 자주 사용하는 명령어
# 이미지 관리
docker images # 이미지 목록
docker rmi agent-api:latest # 이미지 삭제
docker image prune # 미사용 이미지 정리
# 컨테이너 관리
docker ps # 실행 중 컨테이너
docker ps -a # 모든 컨테이너 (중지 포함)
docker logs -f my-agent # 실시간 로그
docker exec -it my-agent /bin/bash # 컨테이너 내부 접속
# 디버깅
docker inspect my-agent # 상세 정보 (네트워크, 볼륨 등)
docker stats # CPU, 메모리 사용량 실시간8 관련 주제
선행 지식
- Docker 설치 – Docker 설치와 기본 설정
후속 주제
- ASGI와 uvicorn – Docker CMD에서 실행하는 Python 서버
다른 카테고리 연결
- FastAPI 입문 – Docker에서 서빙할 백엔드
- CORS와 Proxy – 프로덕션 Reverse Proxy 구성