프로젝트 구조
my-app/
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
│ └── main.py
├── nginx/
│ └── nginx.conf
├── docker-compose.yml # 개발 환경
├── docker-compose.prod.yml # 프로덕션 환경
└── .env.example
Backend Dockerfile (멀티스테이지)
# backend/Dockerfile
FROM python:3.12-slim AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# 의존성 설치 스테이지
FROM base AS dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 개발 스테이지
FROM dependencies AS development
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# 프로덕션 스테이지
FROM dependencies AS production
COPY . .
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
FastAPI 앱
# backend/app/main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
import asyncpg
import redis.asyncio as aioredis
import os
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.db = await asyncpg.create_pool(os.environ["DATABASE_URL"])
app.state.cache = await aioredis.from_url(os.environ["REDIS_URL"])
yield
await app.state.db.close()
await app.state.cache.close()
app = FastAPI(lifespan=lifespan)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users")
async def list_users():
async with app.state.db.acquire() as conn:
rows = await conn.fetch("SELECT id, name, email FROM users ORDER BY id")
return [dict(r) for r in rows]
개발 환경 docker-compose.yml
version: "3.9"
services:
api:
build:
context: ./backend
target: development
ports:
- "8000:8000"
env_file: .env
volumes:
- ./backend:/app # 소스 실시간 반영
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432" # 개발 시 직접 접속 허용
healthcheck:
test: pg_isready -U ${DB_USER}
interval: 5s
retries: 10
cache:
image: redis:7-alpine
ports:
- "6379:6379"
adminer:
image: adminer
ports:
- "8080:8080"
profiles: [tools]
volumes:
db_data:
Nginx 설정
# nginx/nginx.conf
events { worker_connections 1024; }
http {
upstream api {
server api:8000;
}
server {
listen 80;
# API 요청
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 정적 파일
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
}
프로덕션 docker-compose.prod.yml
services:
api:
image: ${REGISTRY}/my-app-api:${VERSION:-latest}
restart: always
env_file: .env.prod
db:
image: postgres:16-alpine
restart: always
volumes:
- db_data:/var/lib/postgresql/data
env_file: .env.prod
# 포트 외부 노출 없음
cache:
image: redis:7-alpine
restart: always
nginx:
image: nginx:alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro
- certs:/etc/letsencrypt
volumes:
db_data:
certs:
Docker 기초 시리즈 정리
| 편 | 주제 |
|---|---|
| 1편 | Docker 개념, 컨테이너 vs VM |
| 2편 | Dockerfile 작성, 멀티스테이지 |
| 3편 | Docker Compose, 멀티 서비스 |
| 4편 | 볼륨과 네트워크 |
| 5편 | 레지스트리와 CI/CD |
| 6편 | 실전 프로젝트 |
Docker를 익히면 개발 환경 설정 시간이 docker compose up 한 줄로 줄어듭니다.
다음은 LLM 파인튜닝 — 모델을 내 데이터로 커스터마이징하는 방법을 배웁니다.