mirim-app/deploy/docker-compose.prod.yml
AWESOMEDEV 0a4c769d00 feat: 고도화 배치 7건 — 보안헤더·다운감시·로그회전·개인정보파기·문서정합성
[보안]
- Caddy 보안 응답헤더(HSTS·X-Frame-Options:DENY·X-Content-Type-Options·Referrer-Policy) +
  Server 헤더 제거. :80 catch-all 제거로 HTTP→HTTPS 자동 리다이렉트 복원(평문 표면 제거)
[운영 안정성]
- 헬스체크 스크립트 + systemd 타이머(2분): 공개 URL 두드려 UP/DOWN 변화 시에만 텔레그램 알림
- Docker 로그 회전(json-file 10m×3, YAML 앵커 공통적용) — 디스크 풀로 인한 조용한 다운 방지
[개인정보]
- 학생 파기: DELETE /api/mentor/students/{id} (멘토 권한) → 자식6테이블 먼저·계정 나중,
  @Transactional. 동의화면이 약속한 '파기' 이행. 멘토 화면에 이름입력 확인 파기 버튼
  StudentPurgeService + 테스트3(삭제순서·멘토거부·404)
[문서 정합성 — 지금 쓰는 학생 대상]
- STUDY.md: 없는 파일 가리키던 코드지도 정정(controller/→web/, ChecklistPage→DashboardPage,
  DocumentsPage→DocsPage, SubmissionsPage→AssignmentsPage)
- README: '6주'→8주, 학습센터/퀴즈/코딩 소개 추가, 폴더구조·초기계정(멘토만 시드·랜덤비번) 현행화
- 퀴즈 만점 안내: 없는 '코스 완료' 버튼 대신 '[← 코스로] 돌아가 완료' 로 정정

검증: 테스트29 통과, 운영에서 헤더4종·308리다이렉트·헬스체크·파기 E2E(무인증401·멘토200·
자식전체0건) 확인. 나머지 13건은 티켓 풀 확장감으로 남김.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:00 +09:00

105 lines
3.9 KiB
YAML

# 운영 배포용 컴포즈 — EC2에서 이 파일 하나로 전체 스택이 뜹니다.
# 사용법: cd deploy && cp .env.example .env (비밀번호 수정) && docker compose -f docker-compose.prod.yml up -d --build
# 학습 포인트: 개발(로컬)과 운영의 차이 —
# - DB 포트를 외부에 안 엽니다(컨테이너끼리 내부 네트워크로만 통신)
# - 비밀번호는 .env 파일로 주입 (코드/이미지에 안 넣음)
# - 재부팅해도 자동 시작(restart: unless-stopped)
# 학습 포인트 — 로그도 자원이다(디스크). Docker 기본값은 로그를 "무제한"으로 쌓아서,
# 작은 EC2 디스크가 몇 달이면 꽉 차고 그 순간 스택 전체가 조용히 멈춘다("아무도 모르는 다운").
# 아래 앵커(&logging)로 각 서비스에 "파일당 10MB, 3개까지만" 회전 정책을 공통 적용한다.
# YAML 앵커(&이름)와 병합(<<: *이름)은 "같은 설정을 한 번 쓰고 여러 곳에 붙이는" 문법이다.
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
postgres:
image: postgres:17-alpine
container_name: mirim-postgres
logging: *logging
environment:
POSTGRES_DB: mirim
POSTGRES_USER: mirim
POSTGRES_PASSWORD: ${DB_PASSWORD:?deploy/.env에 DB_PASSWORD를 설정하세요}
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mirim"]
interval: 10s
timeout: 5s
retries: 5
backend:
build: ../backend
container_name: mirim-backend
logging: *logging
environment:
DB_HOST: postgres # 학습 포인트: 컨테이너끼리는 서비스 이름이 곧 주소예요
DB_PORT: 5432
DB_NAME: mirim
DB_USER: mirim
DB_PASSWORD: ${DB_PASSWORD}
COOKIE_SECURE: "true" # 운영은 HTTPS(Caddy)이므로 세션 쿠키를 HTTPS 전용으로
LOG_LEVEL: INFO # 운영 로그는 INFO
# 텔레그램 알림 (값은 .env에 — 비어 있으면 알림만 꺼지고 앱은 정상)
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
# 멘토 최초 비밀번호 (첫 부팅 시드용). 비우면 랜덤 생성돼 로그에 1회 출력된다.
MENTOR_INITIAL_PASSWORD: ${MENTOR_INITIAL_PASSWORD:-}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
# 프론트 빌드 결과물을 공유 볼륨(webroot)에 복사해 주고 종료하는 일회성 컨테이너
frontend-build:
build: ../frontend
container_name: mirim-frontend-build
volumes:
- webroot:/out
restart: "no"
caddy:
image: caddy:2-alpine
container_name: mirim-caddy
logging: *logging
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- webroot:/srv:ro
- caddy-data:/data # HTTPS 인증서 보관 (재시작해도 유지)
depends_on:
- backend
- frontend-build
restart: unless-stopped
# Gitea — 가벼운 사내 Git 서버. 학생들이 clone/commit/push를 연습하는 곳.
# 학습 포인트: GitHub 같은 서비스를 우리 서버 안에 직접 띄운 것. 웹 UI는 :3000.
gitea:
image: gitea/gitea:1.23
container_name: mirim-gitea
logging: *logging
environment:
GITEA__server__ROOT_URL: http://${GIT_HOST:-edu.awesomedevapp.com}:3000/
GITEA__server__SSH_PORT: "222"
GITEA__service__DISABLE_REGISTRATION: "false" # 학생 스스로 가입 가능
GITEA__security__INSTALL_LOCK: "true" # 웹 설치 마법사 건너뛰기(설정은 env로 완료)
ports:
- "3000:3000" # 웹 UI + HTTP clone/push
- "222:22" # SSH clone/push (선택)
volumes:
- gitea-data:/data
restart: unless-stopped
volumes:
pgdata:
webroot:
caddy-data:
gitea-data: