이 글은 시리즈 5편 · 피날레입니다.
- 1편: 왜 Python이 2026년에도 압도적 1위인가
- 2편: 나만의 AI 챗봇 만들기 — RAG 시스템 처음부터 배포까지
- 3편: 혼자 일하는 AI는 이제 옛날 — LangGraph 멀티 에이전트
- 4편: AI가 드디어 기억한다 — LangGraph 메모리 완벽 가이드
- 5편: 모든 것을 하나로 — Docker 패키징 & 클라우드 배포 완전 정복 ← 지금 여기
📌 난이도: 중급 (1~4편을 따라왔다면 충분합니다) ⏱️ 읽는 시간: 약 13분 / 실습 시간: 약 2~3시간 🛠️ 완성물: RAG + 멀티 에이전트 + 메모리가 통합된 AI 서비스, Docker로 패키징해서 클라우드에 배포
드디어 마지막입니다.
1편에서 Python이 왜 2026년을 지배하는지 이해했습니다. 2편에서 RAG 챗봇을 만들었습니다. 3편에서 멀티 에이전트 팀을 조립했습니다. 4편에서 기억하는 AI를 완성했습니다.
그런데 지금까지 만든 것들은 전부 내 컴퓨터 안에만 있습니다. 친구에게 링크를 보낼 수도 없고, 스마트폰에서 쓸 수도 없습니다.
오늘은 그걸 바꿉니다.
Docker로 패키징하고, 클라우드에 배포해서, 세상 어디서든 접근 가능한 AI 서비스를 완성합니다. 시리즈 1~4편에서 만든 모든 것이 하나로 합쳐지는 날입니다.
📊 목차
- 왜 Docker인가 — “내 컴퓨터에서는 됐는데”를 영원히 없애는 법
- 전체 아키텍처 한눈에 보기
- 프로젝트 구조 정리
- 멀티스테이지 Dockerfile 작성 — 이미지 크기 1GB → 180MB
- Docker Compose로 로컬 전체 스택 실행
- 환경변수 & 시크릿 안전하게 관리하기
- GitHub Actions로 CI/CD 파이프라인 구축
- Railway에 배포 — 프로덕션 라이브
- 헬스체크 & 모니터링
- 배포 후 체크리스트
1. 왜 Docker인가
개발자라면 한 번쯤 이 말을 들어봤을 겁니다.
“내 컴퓨터에서는 잘 됐는데요…”
팀원의 MacBook, AWS EC2 서버, Railway 컨테이너 — 각각 Python 버전이 다르고, 패키지 버전이 다르고, 환경변수 설정이 다릅니다. 그래서 로컬에서 된 코드가 서버에서 안 되는 일이 생깁니다.
Docker는 이 문제를 근본적으로 해결합니다.
내 코드 + 실행 환경 + 의존성을 하나의 컨테이너 이미지로 묶어버립니다. 이 이미지는 어디서 실행해도 동일하게 동작합니다. Mac이든, Linux 서버든, 클라우드든.
[Docker 없이]개발자 Mac → "잘 돼요!"CI 서버 → "패키지 없음 오류"프로덕션 → "Python 버전 다름"팀원 PC → "의존성 충돌"[Docker 있으면]어디서 실행해도 → 동일한 결과 ✅
2026년 프로덕션 Python AI 앱 배포의 표준은 명확합니다: Docker 컨테이너입니다.
2. 전체 아키텍처 한눈에 보기
오늘 완성할 시스템의 전체 그림입니다.
┌─────────────────────────────────────────────────────┐│ 클라이언트 (브라우저/앱) │└──────────────────────────┬──────────────────────────┘ │ HTTPS┌──────────────────────────▼──────────────────────────┐│ Railway 클라우드 플랫폼 ││ ┌───────────────────────────────────────────────┐ ││ │ FastAPI 컨테이너 (포트 8000) │ ││ │ ┌─────────────┐ ┌──────────────────────┐ │ ││ │ │ RAG 파이프라인│ │ LangGraph 에이전트 팀 │ │ ││ │ │ (2편) │ │ + 메모리 (3~4편) │ │ ││ │ └──────┬──────┘ └──────────┬───────────┘ │ ││ └─────────┼───────────────────┼────────────────┘ ││ │ │ ││ ┌─────────▼───────────────────▼────────────────┐ ││ │ PostgreSQL 컨테이너 (체크포인터 + 벡터DB) │ ││ └──────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────┘
서비스 구성:
- FastAPI 앱 — 모든 엔드포인트 처리
- PostgreSQL — 대화 기억(체크포인터) + 벡터 저장(pgvector)
- Railway — 컨테이너 호스팅, 자동 HTTPS, 도메인 제공
3. 프로젝트 구조 정리
1~4편에서 만든 파일들을 하나의 프로젝트로 통합합니다.
ai-service/├── .env # 로컬 개발용 (절대 커밋 금지!)├── .env.example # 팀원 공유용 예시 파일├── .gitignore├── Dockerfile # 프로덕션 이미지 정의├── docker-compose.yml # 로컬 전체 스택 실행├── requirements.txt├── app/│ ├── main.py # FastAPI 진입점│ ├── rag_pipeline.py # 2편: RAG 로직│ ├── agents/│ │ ├── state.py # 3편: 공유 상태│ │ ├── agents.py # 3편: 에이전트 3개│ │ ├── supervisor.py # 3편: 슈퍼바이저│ │ └── graph.py # 3편: 그래프 조립│ └── memory/│ └── checkpointer.py # 4편: PostgreSQL 체크포인터├── .github/│ └── workflows/│ └── deploy.yml # CI/CD 파이프라인└── scripts/ └── healthcheck.sh # 헬스체크 스크립트
4. 멀티스테이지 Dockerfile — 이미지 1GB → 180MB
가장 중요한 파일입니다. 멀티스테이지 빌드를 쓰면 최종 이미지 크기를 80% 이상 줄일 수 있습니다.
# Dockerfile# ─────────────────────────────────────# Stage 1: 빌더 (빌드 도구 포함)# ─────────────────────────────────────FROM python:3.12-slim AS builder# Python 최적화 환경변수ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1WORKDIR /build# 빌드 도구 설치 (최종 이미지에는 포함 안 됨)RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ gcc \ && rm -rf /var/lib/apt/lists/*# 가상환경 생성 & 의존성 설치RUN python -m venv /opt/venvENV PATH="/opt/venv/bin:$PATH"COPY requirements.txt .RUN pip install --upgrade pip && \ pip install -r requirements.txt# ─────────────────────────────────────# Stage 2: 프로덕션 (빌드 도구 제외)# ─────────────────────────────────────FROM python:3.12-slim AS productionENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH="/opt/venv/bin:$PATH"# 빌더에서 가상환경만 복사 (빌드 도구는 제외)COPY --from=builder /opt/venv /opt/venv# 보안: root가 아닌 전용 사용자로 실행RUN useradd --create-home --shell /bin/bash appuserWORKDIR /home/appuser/app# 앱 코드 복사COPY --chown=appuser:appuser app/ .USER appuserEXPOSE 8000# 프로덕션 서버: gunicorn + uvicorn workersCMD ["gunicorn", "main:app", \ "--worker-class", "uvicorn.workers.UvicornWorker", \ "--workers", "2", \ "--bind", "0.0.0.0:8000", \ "--timeout", "120", \ "--access-logfile", "-"]
왜 멀티스테이지인가?
| 싱글 스테이지 | 멀티 스테이지 | |
|---|---|---|
| 이미지 크기 | ~1.0 GB | ~180 MB |
| 빌드 도구 포함 | ✅ (보안 위험) | ❌ (제거됨) |
| 배포 속도 | 느림 | 빠름 |
| 보안 취약면 | 넓음 | 좁음 |
5. Docker Compose로 로컬 전체 스택 실행
로컬에서 앱 + DB를 한 번에 띄우는 설정입니다.
# docker-compose.ymlversion: "3.9"services: # ── 메인 앱 ───────────────────────────────── app: build: context: . target: production # Dockerfile의 production 스테이지 사용 ports: - "8000:8000" environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - DATABASE_URL=postgresql://aiuser:aipass@db:5432/aidb - ENVIRONMENT=development depends_on: db: condition: service_healthy # DB 준비될 때까지 대기 volumes: - ./app:/home/appuser/app # 개발 중 코드 변경 즉시 반영 - uploads_data:/home/appuser/app/uploads restart: unless-stopped # ── PostgreSQL DB ──────────────────────────── db: image: pgvector/pgvector:pg16 # pgvector 확장 포함 버전 environment: POSTGRES_USER: aiuser POSTGRES_PASSWORD: aipass POSTGRES_DB: aidb ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U aiuser -d aidb"] interval: 5s timeout: 5s retries: 5 restart: unless-stoppedvolumes: postgres_data: uploads_data:
실행 명령:
# 전체 스택 시작 (백그라운드)docker-compose up -d# 로그 확인docker-compose logs -f app# 종료docker-compose down# 완전 초기화 (볼륨까지 삭제)docker-compose down -v
http://localhost:8000/docs 에 접속하면 Swagger UI로 바로 테스트할 수 있습니다.
6. 환경변수 & 시크릿 안전하게 관리하기
API 키를 코드에 직접 쓰거나 GitHub에 올리면 큰일 납니다. 올바른 관리 방법입니다.
# .env.example — 팀원 공유용 (실제 값 없이 키 이름만)ANTHROPIC_API_KEY=your-api-key-hereDATABASE_URL=postgresql://user:password@localhost:5432/aidbENVIRONMENT=developmentSECRET_KEY=generate-a-random-32-char-string
# .gitignore — 반드시 포함.env.env.local*.dbuploads/vector_store/__pycache__/*.pyc.DS_Store
# app/config.py — 설정 중앙 관리from pydantic_settings import BaseSettingsfrom functools import lru_cacheclass Settings(BaseSettings): # 필수 설정 (없으면 앱 시작 안 됨) anthropic_api_key: str database_url: str # 선택 설정 (기본값 있음) environment: str = "production" secret_key: str = "change-this-in-production" max_upload_size_mb: int = 10 max_conversation_turns: int = 50 class Config: env_file = ".env" case_sensitive = False@lru_cache() # 한 번만 로드, 이후 캐시 사용def get_settings() -> Settings: return Settings()# 사용법:# from config import get_settings# settings = get_settings()# settings.anthropic_api_key
7. GitHub Actions로 CI/CD 파이프라인 구축
코드를 push할 때마다 자동으로 테스트하고 배포하는 파이프라인입니다.
# .github/workflows/deploy.ymlname: Deploy AI Serviceon: push: branches: [main] # main 브랜치 push 시 실행 pull_request: branches: [main] # PR 시 테스트만 실행jobs: # ── 1단계: 테스트 ──────────────────────────── test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip" - name: Install dependencies run: pip install -r requirements.txt pytest httpx - name: Run tests run: pytest app/tests/ -v --tb=short env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} DATABASE_URL: "sqlite:///./test.db" # ── 2단계: Docker 빌드 & 푸시 ────────────────── build: needs: test # 테스트 통과 후에만 실행 runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Build Docker image run: docker build --target production -t ai-service:${{ github.sha }} . - name: Verify image starts run: | docker run --rm -d --name test-container \ -e ANTHROPIC_API_KEY=dummy \ -e DATABASE_URL=dummy \ -p 8000:8000 \ ai-service:${{ github.sha }} sleep 5 curl -f http://localhost:8000/health || exit 1 docker stop test-container # ── 3단계: Railway 배포 ────────────────────── deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Install Railway CLI run: npm install -g @railway/cli - name: Deploy to Railway run: railway up --service ai-service env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
GitHub Secrets 설정:
- Repository → Settings → Secrets → Actions
ANTHROPIC_API_KEY추가RAILWAY_TOKEN추가 (Railway 대시보드에서 발급)
8. Railway에 배포 — 프로덕션 라이브
Railway는 Docker 이미지를 그대로 호스팅할 수 있는 플랫폼입니다. GitHub과 연동하면 코드 push만으로 자동 배포됩니다.
최초 배포 순서:
# 1. Railway CLI 설치npm install -g @railway/cli# 2. 로그인railway login# 3. 새 프로젝트 생성railway init# 4. PostgreSQL 서비스 추가railway add --plugin postgresql# 5. 환경변수 설정 (한 번에 여러 개)railway variables set \ ANTHROPIC_API_KEY=your-key \ ENVIRONMENT=production \ SECRET_KEY=$(openssl rand -hex 32)# 6. 배포!railway up
배포 후 확인:
# 배포된 URL 확인railway open# 실시간 로그 보기railway logs --tail# 환경변수 목록 확인railway variables
Railway는 자동으로 HTTPS와 도메인을 제공합니다. 배포가 완료되면 https://your-service.up.railway.app 형태의 URL로 접근 가능합니다.
9. 헬스체크 & 모니터링
배포 후 서비스가 정상 작동하는지 확인하는 엔드포인트입니다.
# app/main.py에 추가from fastapi import FastAPIfrom datetime import datetimeimport osapp = FastAPI()@app.get("/health")async def health_check(): """ 로드밸런서, Railway, 모니터링 도구가 이 엔드포인트를 주기적으로 호출합니다. 200 응답 = 정상, 5xx = 비정상 → 자동 재시작 """ return { "status": "healthy", "timestamp": datetime.utcnow().isoformat(), "environment": os.getenv("ENVIRONMENT", "unknown"), "version": "1.0.0" }@app.get("/health/detailed")async def detailed_health(): """DB 연결까지 확인하는 상세 헬스체크""" checks = {} # DB 연결 확인 try: from memory.checkpointer import get_db_connection conn = get_db_connection() conn.close() checks["database"] = "ok" except Exception as e: checks["database"] = f"error: {str(e)}" # 전체 상태 판단 all_ok = all(v == "ok" for v in checks.values()) status_code = 200 if all_ok else 503 return JSONResponse( content={"status": "healthy" if all_ok else "degraded", "checks": checks}, status_code=status_code )
10. 배포 후 체크리스트
서비스를 공개하기 전 반드시 확인해야 할 항목들입니다.
보안
- [ ]
.env파일이.gitignore에 포함되어 있는가? - [ ] API 키가 코드에 하드코딩되어 있지 않은가?
- [ ] Docker 컨테이너가 root가 아닌 일반 사용자로 실행되는가?
- [ ] HTTPS가 적용되어 있는가? (Railway 자동 제공)
성능
- [ ] Gunicorn worker 수가 적절한가? (CPU 코어 수 × 2 + 1 권장)
- [ ] 응답 시간이 3초 이내인가?
- [ ] 메모리 사용량이 허용 범위 내인가?
신뢰성
- [ ]
/health엔드포인트가 200을 반환하는가? - [ ] DB 연결이 정상인가?
- [ ] 에러 로그에 이상한 항목이 없는가?
운영
- [ ] 로그를 확인하는 방법을 알고 있는가?
- [ ] 서비스 재시작 방법을 알고 있는가?
- [ ] 비용 알림이 설정되어 있는가?
마치며 — 끝이 아니라 시작입니다
5편에 걸쳐 우리가 함께 만든 것들을 돌아봅니다.
1편 — Python이 AI 시대를 지배하는 이유를 이해했습니다. 2편 — RAG로 문서 기반 AI 챗봇을 만들었습니다. 3편 — 멀티 에이전트로 AI 팀을 구성했습니다. 4편 — 메모리를 붙여서 기억하는 AI를 완성했습니다. 5편 — Docker와 클라우드로 세상에 내보냈습니다.
이게 끝이 아닙니다.
배포된 서비스를 운영하면서 진짜 사용자의 피드백을 받고, 병목을 발견하고, 개선하는 과정이 이제 시작됩니다. 그리고 그 과정이 개발자를 성장시킵니다.
다음에 만들 것들: 스트리밍 응답, 사용자 인증, 사용량 기반 과금, 멀티모달(이미지 + 텍스트) 처리 — 가능성은 무한합니다.
이 시리즈를 끝까지 따라온 모든 분께 진심으로 감사드립니다. 질문이 있으면 댓글로 남겨주세요 🙌
🔖 시리즈 전체 목록
- 1편: 왜 Python이 2026년에도 압도적 1위인가
- 2편: 나만의 AI 챗봇 만들기 — RAG 처음부터 배포까지
- 3편: 혼자 일하는 AI는 이제 옛날 — LangGraph 멀티 에이전트
- 4편: AI가 드디어 기억한다 — LangGraph 메모리 완벽 가이드
- 5편: 모든 것을 하나로 — Docker & 클라우드 배포 ← 지금 여기
태그: #Python #Docker #FastAPI #LangGraph #클라우드배포 #Railway #CI/CD #AI서비스 #개발튜토리얼 #2026
데이터 출처: Docker Multi-Stage Builds Guide · Railway Docs · FastAPI Deployment Guide 2026 · GitHub Actions Docs