DockerDocker 기초 · 6기초

Docker 실전 프로젝트 — 전체 스택 컨테이너화

Docker프로젝트FastAPIPostgreSQLNginx전체스택

프로젝트 구조

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 파인튜닝 — 모델을 내 데이터로 커스터마이징하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.