mirim-app/deploy/docker-compose.prod.yml
AWESOMEDEV 92e65760bc
All checks were successful
CI / backend-test (push) Successful in 2m6s
CI / frontend-build (push) Successful in 1m4s
ci: 러너를 compose로 관리 + CI.md 문서 + OP-02 티켓 '구현됨'으로 갱신
- act_runner를 docker-compose.prod.yml에 편입(등록 정보는 /opt/mirim-runner에 영속)
  → compose down/up에도 관리되고, 재등록 없이 이어짐. 재연결 확인
- deploy/CI.md: 구성요소·동작흐름·재구축 절차·함정 메모(Maven archive·clone add-host·checkout node)
- 티켓 풀 OP-02를 ' 구현됨 — 확장(프론트 테스트·캐시·브랜치 보호)은 학생 몫'으로 갱신

CI 상태: run 2 백엔드·프론트 둘 다 초록. run 1 빨강→run 2 초록으로 게이트 동작 실증.

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

125 lines
4.8 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로 완료)
GITEA__actions__ENABLED: "true" # CI — Gitea Actions(push 시 자동 테스트) 켜기
ports:
- "3000:3000" # 웹 UI + HTTP clone/push
- "222:22" # SSH clone/push (선택)
volumes:
- gitea-data:/data
restart: unless-stopped
# CI 러너 — push마다 .gitea/workflows/ci.yml을 실행한다(Gitea Actions).
# 학습 포인트: 러너는 "일감을 받아 job 컨테이너를 띄우는 일꾼"이라 Docker 소켓을 마운트한다.
# 최초 등록은 /opt/mirim-runner에 .runner 파일로 저장되므로(호스트에 준비됨),
# 컨테이너를 다시 만들어도 재등록 없이 이어진다. 설정은 /opt/mirim-runner/config.yaml.
runner:
image: gitea/act_runner:0.2.11
container_name: mirim-runner
logging: *logging
depends_on:
- gitea
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/mirim-runner:/data
working_dir: /data
environment:
GITEA_INSTANCE_URL: http://gitea:3000
CONFIG_FILE: /data/config.yaml
restart: unless-stopped
volumes:
pgdata:
webroot:
caddy-data:
gitea-data: