모든 것을 하나로 — Docker 패키징부터 클라우드 배포까지 완전 정복 [2026 실전 튜토리얼]

이 글은 시리즈 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편에서 만든 모든 것이 하나로 합쳐지는 날입니다.


📊 목차

  1. 왜 Docker인가 — “내 컴퓨터에서는 됐는데”를 영원히 없애는 법
  2. 전체 아키텍처 한눈에 보기
  3. 프로젝트 구조 정리
  4. 멀티스테이지 Dockerfile 작성 — 이미지 크기 1GB → 180MB
  5. Docker Compose로 로컬 전체 스택 실행
  6. 환경변수 & 시크릿 안전하게 관리하기
  7. GitHub Actions로 CI/CD 파이프라인 구축
  8. Railway에 배포 — 프로덕션 라이브
  9. 헬스체크 & 모니터링
  10. 배포 후 체크리스트

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=1
WORKDIR /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/venv
ENV 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 production
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH"
# 빌더에서 가상환경만 복사 (빌드 도구는 제외)
COPY --from=builder /opt/venv /opt/venv
# 보안: root가 아닌 전용 사용자로 실행
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /home/appuser/app
# 앱 코드 복사
COPY --chown=appuser:appuser app/ .
USER appuser
EXPOSE 8000
# 프로덕션 서버: gunicorn + uvicorn workers
CMD ["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.yml
version: "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-stopped
volumes:
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-here
DATABASE_URL=postgresql://user:password@localhost:5432/aidb
ENVIRONMENT=development
SECRET_KEY=generate-a-random-32-char-string
# .gitignore — 반드시 포함
.env
.env.local
*.db
uploads/
vector_store/
__pycache__/
*.pyc
.DS_Store
# app/config.py — 설정 중앙 관리
from pydantic_settings import BaseSettings
from functools import lru_cache
class 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.yml
name: Deploy AI Service
on:
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 FastAPI
from datetime import datetime
import os
app = 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