🎯 프로젝트 소개

Teach AI는 인간이 AI에게 철학적 질문을 던지고, AI가 인간의 답변을 통해 배우는 프로젝트예요.

  • URL: https://teach.chloe.ai.kr
  • Stack: FastAPI + React + PostgreSQL + Docker
  • 인프라: Caddy WAF → Traefik → Docker Containers

🚨 배포 시 마주친 3가지 문제

1️⃣ 첫 번째 장애물: nginx가 backend를 못 찾아요

증상:

nginx: [emerg] host not found in upstream "backend"

원인: Frontend 컨테이너가 teach-internal 네트워크에만 연결되어 있어서 backend에 접근 불가.

해결:

1
2
3
4
frontend:
  networks:
    - proxy         # Traefik 연결용
    - teach-internal  # Backend 연결용

nginx 설정에 Docker DNS resolver도 추가:

1
2
3
4
5
location /api/ {
    resolver 127.0.0.11 valid=30s;
    set $backend_upstream http://backend:8000;
    proxy_pass $backend_upstream;
}

2️⃣ 두 번째 장애물: SSL Protocol Error

증상:

1
2
curl https://teach.chloe.ai.kr
# error:0A000438:SSL routines::tlsv1 alert internal error

원인: Traefik 설정이 traefik-public 네트워크 & websecure 엔트리포인트를 사용하려 했지만, 실제 환경은:

  • 네트워크: proxy
  • 엔트리포인트: web (8080)

해결:

1
2
3
4
5
labels:
  - "traefik.enable=true"
  - "traefik.docker.network=proxy"  # ✅ 실제 네트워크명
  - "traefik.http.routers.teach.entrypoints=web"  # ✅ 실제 엔트리포인트
  - "traefik.http.routers.teach.rule=Host(`teach.chloe.ai.kr`)"

3️⃣ 세 번째 장애물: Frontend가 unhealthy 상태

증상:

1
2
docker ps
# teach-frontend   Up 5 minutes (unhealthy)

원인: healthcheck가 IPv6 localhost로 연결 시도 → 실패

1
HEALTHCHECK CMD wget --spider http://localhost/  # ❌ ::1로 연결 시도

해결:

1
HEALTHCHECK CMD wget --spider http://127.0.0.1/  # ✅ IPv4 명시

Traefik은 unhealthy 컨테이너를 라우팅에서 제외하므로, 이게 해결되자 즉시 정상 작동!


🎓 배운 것들

Docker Networking 101

  1. 멀티 네트워크 전략:

    • proxy: 외부 노출용 (Traefik 연결)
    • teach-internal: 내부 통신용 (DB, Backend)
  2. 네트워크 순서 중요:

    1
    2
    3
    
    networks:
      - proxy         # 먼저 선언된 게 primary
      - teach-internal
    

Caddy + Traefik 조합

사용자 (HTTPS)
  ↓
Caddy WAF (80/443) - 자동 Let's Encrypt 발급
  ↓
Traefik (8080) - Docker label 기반 라우팅
  ↓
Container

핵심 포인트:

  • Caddy: /config/caddy/Caddyfile (마운트 경로 확인 필수!)
  • Traefik: traefik.docker.network 라벨로 명시적 네트워크 지정

Healthcheck 함정

1
2
3
4
5
# ❌ 잘못된 예시 (IPv6로 시도)
CMD wget --spider http://localhost/

# ✅ 올바른 예시 (IPv4 명시)
CMD wget --spider http://127.0.0.1/

Alpine Linux에서 localhost는 IPv6를 먼저 시도하는데, nginx가 IPv6를 비활성화한 경우 실패!


📊 최종 아키텍처

Internet
  ↓ HTTPS (443)
┌─────────────────────┐
│   Caddy WAF         │ - Let's Encrypt 자동 발급
│  (Coraza CRS)       │ - WAF 룰 적용
└──────────┬──────────┘
           ↓ HTTP (8080)
┌─────────────────────┐
│   Traefik           │ - Docker label 기반 라우팅
│  (Reverse Proxy)    │ - proxy 네트워크
└──────────┬──────────┘
           ↓
┌──────────┴──────────┐
│  teach-frontend     │ ← nginx (React SPA)
│  (proxy +           │
│   teach-internal)   │
└──────────┬──────────┘
           ↓ /api/*
┌─────────────────────┐
│  teach-backend      │ ← FastAPI (8000)
│  (proxy +           │
│   teach-internal)   │
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│  teach-db           │ ← PostgreSQL 16
│  (teach-internal)   │
└─────────────────────┘

🚀 결과

소요 시간: 약 1시간 (문제 진단 + 해결)

해결 순서:

  1. nginx DNS resolver 추가 (5분)
  2. Traefik 네트워크 & 엔트리포인트 수정 (10분)
  3. Caddy 설정 추가 & reload (5분)
  4. Frontend healthcheck 수정 (10분)
  5. Traefik 재시작 & 최종 확인 (5분)

최종 상태:

  • ✅ HTTPS 정상 작동 (Let’s Encrypt 자동 발급)
  • ✅ 모든 컨테이너 healthy
  • ✅ Traefik 라우팅 정상
  • ✅ WAF 보호 활성화

💡 Tips

1. Caddy 설정 reload 시 주의사항

1
2
3
4
5
6
# ❌ 잘못된 경로
docker exec waf caddy reload --config /etc/caddy/Caddyfile

# ✅ 마운트된 실제 경로 확인
docker inspect waf | jq -r '.[0].Mounts[] | "\(.Source) -> \(.Destination)"'
docker exec waf caddy reload --config /config/caddy/Caddyfile

2. Traefik 디버깅

1
2
3
4
5
# 현재 로드된 라우터 확인
docker logs traefik 2>&1 | grep -i "teach"

# 네트워크 우선순위 경고 확인
# "Defaulting to first available network" 메시지 주의!

3. healthcheck 빠른 테스트

1
2
3
4
# 컨테이너 내부에서 직접 실행
docker exec teach-frontend wget --spider http://127.0.0.1/

# 성공하면 healthcheck도 성공!

🤔 회고

좋았던 점:

  • TDD로 Backend를 먼저 완성해둬서 문제가 인프라에만 집중됨
  • 에러 로그가 명확해서 문제 진단이 빨랐음
  • Docker Compose 멀티 네트워크 전략 이해도 상승

아쉬웠던 점:

  • 환경마다 다른 Traefik 설정 (websecure vs web)
  • localhost IPv6 함정을 미리 예상하지 못함
  • Caddy 마운트 경로 확인 누락

다음엔 개선할 점:

  • 프로젝트 템플릿에 healthcheck 예제 추가
  • Traefik 설정 문서화 (네트워크/엔트리포인트 명시)
  • Caddy 설정 경로 환경변수로 통일

📚 관련 글


한줄 요약: Docker 네트워크와 healthcheck는 작은 실수가 큰 장애를 만든다. 로그를 믿고, 문서를 확인하고, 차근차근 해결하면 된다! 🚀