시스템·인프라 아키텍처¶
코드/시스템 3문서 — 백엔드 코드 아키텍처 = 서버 코드 구조(헥사고날·JPA·JWT) · 프론트엔드 코드 아키텍처 = 화면 코드 구조(FSD·라우팅·IA) · 시스템·인프라 아키텍처(이 문서) = 무엇이 어떻게 연결되고, 어디서 돌고, 어떻게 배포·운영되나(전체 지도 + 서버·CI/CD·시크릿·DB·관측성 — 운영의 SSOT).
디에듀는 사용자가 실제로 쓰는 ① 제품 런타임과, 그것을 만들고 운영하는 ② 위키 시스템·③ 마케팅 에이전트로 이루어진다. 이 문서는 먼저 셋의 전체 지도를 보여주고, 이어서 각 시스템을 차례로 — 개발자 관심사 순서대로 제품 → 위키 → 마케팅 — 파고든다. 시크릿·호스트·키 같은 실제 값은 여기 적지 않고 GitHub Actions Secrets와 raw에만 둔다(이 문서는 구조와 정책의 모양만 다룬다).
1. 전체 시스템 지도¶
세 시스템이 GitHub(코드)·Cloudflare(엣지)·Slack/Notion(협업)으로 묶인다.
flowchart TB
subgraph USERS["사용자"]
U1["강사·학생·학부모"]
U2["팀원 (개발·기획·디자인·마케팅)"]
end
subgraph PRODUCT["① 제품 런타임 (§2) — dev/prod 환경 분리"]
FE["프론트엔드 · Next.js<br/>▲ Vercel (dev·prod 공통)"]
BFF["BFF<br/>app/api/v1/[...path]"]
BE["백엔드 · Spring<br/>🏠 dev=온프레 · ☁️ prod=Lightsail"]
DB[("MySQL dedu + Redis<br/>🏠 dev=온프레 · ☁️ prod=Lightsail")]
OBS["관측성<br/>Prometheus·Loki·Grafana"]
FE --> BFF --> BE --> DB
BE -.metrics/logs.-> OBS
end
subgraph WIKI["② 위키 시스템 (§3)"]
WC["wiki/ 마크다운 (정본)"]
SITE["MkDocs → wiki.d-edu.site"]
RAG["RAG 검색"]
PIPE["Slack→Issue→PR"]
WC --> SITE
WC --> RAG
PIPE --> WC
end
subgraph MKT["③ 마케팅 에이전트 (§4)"]
MAI["marketing-AI (OpenClaw)<br/>별도 레포"]
DASH["openclaw-dashboard"]
MAI --> DASH
end
subgraph PLAT["플랫폼·협업"]
GH["GitHub Actions (CI/CD)"]
CF["Cloudflare<br/>Pages·Workers·R2·Tunnel"]
SLACK["Slack"]
NOTION["Notion (MCP)"]
end
U1 --> FE
U2 --> SITE
U2 --> SLACK
GH --> PRODUCT
GH --> SITE
CF --> SITE & RAG & DASH
SLACK --> PIPE
NOTION -.확정본 이관.-> WC
MAI -.유입.-> U1
- ① 제품 런타임 — 프론트(Next.js·Vercel) → BFF → 백엔드(Spring) → MySQL/Redis, 그 위의 관측성(Prometheus·Loki·Grafana)까지. 환경이 둘로 갈린다: 프론트는 dev·prod 모두 Vercel, 백엔드·DB는 🏠 개발=온프레미스 / ☁️ 운영=AWS Lightsail. 코드 구조는 frontend-architecture·backend-architecture, 환경별 배치·배포는 §2(특히 §2 토폴로지 도식).
- ② 위키 시스템 —
wiki/마크다운이 정본, 세 갈래(정적 사이트·RAG 검색·Slack→PR 파이프라인)로 소비된다. §3, 협업 원칙은 ai-collaboration. - ③ 마케팅 에이전트 — 유입을 만드는 별도 레포 시스템. §4.
2. ① 제품 런타임 (사용자가 쓰는 것)¶
사용자가 보는 서비스 그 자체 — 프론트·백엔드·DB·관측성이 도는 곳. dev(개발)와 prod(운영)의 인프라가 다르다: 프론트는 둘 다 Vercel로 띄우되 private 모노레포를 public 미러로 싱크해 빌드하고, 백엔드는 개발=온프레미스 / 운영=AWS Lightsail 인스턴스+DB다.
flowchart TB
subgraph REPO["📦 idealstudy/mvp-mono (private 모노레포)"]
BE["mvp-back/ · Spring Boot"]
FE["mvp-front/ · Next.js"]
end
FE -- "main·develop 머지<br/>front-sync-mirror (subtree split→push)" --> TEP["the-edu-project<br/>(public 미러 · Vercel 소스)"]
TEP -- "Vercel 자동 빌드" --> VPROD & VDEV
BE -- "main 머지 (back-deploy-aws)" --> LIGHT
BE -- "develop 머지 (back-deploy-onprem)" --> ONP
subgraph PRODENV["☁️ 운영 Prod — main"]
VPROD["▲ Vercel<br/>d-edu.site"]
LIGHT["AWS Lightsail 인스턴스<br/>api.d-edu.site"]
PDB[("Lightsail MySQL · dedu")]
LIGHT --- PDB
end
subgraph DEVENV["🏠 개발 Dev — develop"]
VDEV["▲ Vercel<br/>dev.d-edu.site"]
ONP["온프레미스 서버 (Docker)<br/>the-edu-staging · apidev.d-edu.site"]
DDB[("mysql_dev")]
RD[("theedu_redis")]
ONP --- DDB
ONP --- RD
end
VPROD -- "API (BFF 프록시)" --> LIGHT
VDEV -- "API (BFF) · Cloudflare 터널" --> ONP
LIGHT -- "미디어 presigned" --> S3
ONP -- "미디어 presigned" --> S3[("☁️ AWS S3 · theedu (dev·prod 공용)")]
읽는 법: 개발자가 mvp-mono에 PR → 머지. develop 머지면 백엔드는 온프레로, 프론트는 the-edu-project(develop)→Vercel(dev.d-edu.site)로. main 머지면 백엔드는 Lightsail로, 프론트는 the-edu-project(main)→Vercel(d-edu.site)로. 프론트는 어느 쪽이든 Vercel이고, 사용자 화면은 API를 같은 환경 백엔드로 보낸다(dev=온프레 터널, prod=Lightsail).
2-1. 한눈에 — 하이브리드 구성¶
| 구분 | Dev (개발 · develop) |
Prod (운영 · main) |
|---|---|---|
| 백엔드 | 🏠 온프레미스 서버 (Docker) | ☁️ AWS Lightsail 인스턴스 |
| 프론트 | ▲ Vercel | ▲ Vercel |
| 프론트 소스 | the-edu-project develop 브랜치 |
the-edu-project main 브랜치 |
| DB | 온프레 mysql_dev (MySQL 8.4, DB명 dedu) |
Lightsail MySQL dedu |
| Redis | 온프레 theedu_redis |
서버 내 컨테이너 |
| 백엔드 도메인 | apidev.d-edu.site (Cloudflare 터널) |
api.d-edu.site |
| 프론트 도메인 | dev.d-edu.site |
d-edu.site |
| Spring 프로파일 | staging | prod |
| 배포 트리거 | develop 머지 |
main 머지 |
코드는 모노레포 idealstudy/mvp-mono 한 트리에 백엔드·프론트·위키가 함께 있다(ADR-0003). 2026년 5~6월에 Dev 백엔드를 AWS에서 온프레미스로 내렸다 — 클라우드 비용을 없애고 인프라·데이터를 직접 제어하기 위해서다. 그 배경·과정·거기서 얻은 교훈은 §2-9에 자세히 정리했다.
2-2. 구성 자원¶
Dev 백엔드 — 온프레미스 (Docker). 사무실 서버 1대 위 Docker 컨테이너. 외부 접속은 Cloudflare 터널(cloudflared)로 — 서버에 공인 IP/포트 개방 없이 안전.
| 컨테이너 / 자원 | 역할 | 비고 |
|---|---|---|
the-edu-staging |
백엔드 Spring 앱 | GHCR 이미지, staging 프로파일, 포트 8080 |
mysql_dev |
MySQL 8.4 (DB명 dedu) |
~/mysql_dev docker-compose, 데이터=도커 볼륨 |
theedu_redis |
Redis (세션·인증코드) | ~/redis_dev docker-compose |
docker network theedu_backend |
세 컨테이너 내부 통신망 | external 네트워크 (⚠️ §2-8 리스크) |
| self-hosted GitHub Actions runner | 온프레 배포 실행기 | ~/actions-runner |
| cloudflared 터널 | 외부→서버 통로 | apidev.d-edu.site, ssh.d-edu.site |
cloudflared 터널 = 서버가 Cloudflare로 바깥 연결을 걸어두고 외부 요청을 그 연결로 받는 방식. 방화벽 포트를 안 열어도 됨.
프론트엔드 — Vercel (dev·prod 공통). Vercel이 보는 소스는 idealstudy/the-edu-project(public). 모노레포가 이 레포로 미러링(§2-3)되면 Vercel이 브랜치별로 자동 빌드/배포한다 — main → 운영(d-edu.site), develop → 개발(dev.d-edu.site). private 모노레포를 Vercel에 직접 붙이지 않고 public 미러를 거치는 게 핵심.
Prod 백엔드 — AWS Lightsail. Lightsail 인스턴스 + Lightsail MySQL 위에서 백엔드(api.d-edu.site)가 돈다. (과거엔 같은 서버가 nginx+certbot으로 React 정적 빌드까지 서빙했으나, 프론트가 Vercel로 이전되며 Lightsail은 백엔드 전용이 됐다 — 구 방식은 §2-9.)
공용 — AWS S3 theedu. 과제·QnA·게시판 썸네일 같은 미디어 저장소. Dev(온프레)와 운영이 같은 S3를 공유하므로 presigned URL 발급에 AWS 키가 필요하고 이 버킷은 삭제하면 안 된다. 공개/비공개는 targetType으로 S3 키 prefix를 나누고, 공개=CloudFront 고정 URL·비공개=presigned로 내보낼 계획(상세는 backend-architecture §7 미디어).
2-3. 배포 파이프라인 (CI/CD — 코드가 서버로 가는 길)¶
GitHub Actions로 빌드/배포 분리: Actions가 빌드 → 레지스트리/미러 push → 대상이 pull/build·run.
| 대상 | 워크플로 | 트리거 | 흐름 |
|---|---|---|---|
| Dev 백엔드 | back-deploy-onprem.yml |
develop 머지 + mvp-back/** |
self-hosted runner 빌드 → GHCR push → 온프레 docker run the-edu-staging → 헬스 게이트(응답 확인, 실패 시 중단) |
| Prod 백엔드 | back-deploy-aws.yml |
main 머지 |
AWS Lightsail 배포 |
| 프론트 (dev·prod) | front-sync-mirror.yml |
main·develop 머지 + mvp-front/** |
git subtree split로 mvp-front/만 떼어 → SSH(deploy key)로 the-edu-project 같은 브랜치에 force push → Vercel 자동 빌드/배포(main→prod, develop→dev) |
백엔드 빌드 골격: checkout → setup-java(21, temurin) → gradlew clean build -x test → Flyway 검증 → 이미지 빌드 → push → 서버 run.
- subtree split = 모노레포에서
mvp-front/폴더만 별도 히스토리로 뽑아 public 미러에 올려 Vercel이 빌드하게 하는 방식(private 모노레포를 Vercel에 직접 안 붙임). - 컨테이너 구성·Compose 개념(Services/Networks/Volumes/Environment,
${VAR}플레이스홀더), 로컬 IntelliJ 실행·로컬 HTTPS(Caddy 프록시) 가이드도 별도 정리돼 있다. - CI/CD 트러블슈팅(React 빌드시점 env 번들 / Spring 설정 우선순위 / Mixed Content / CORS)·시크릿 설정 상세는 [SENSITIVE] raw 격리.
새 환경변수/시크릿 추가는 컨테이너 실행부가 여러 배포 경로(aws·onprem·Jenkins)에 산발해 한 곳만 고치면 누락된다. 절차·active 경로 매트릭스는 §2-5.
2-4. 도메인 · 네트워크 · 쿠키¶
| 도메인 | 가리키는 곳 | 경유 |
|---|---|---|
d-edu.site |
Prod 프론트 (Vercel) | — |
api.d-edu.site |
Prod 백엔드 | AWS Lightsail |
dev.d-edu.site |
Dev 프론트 (Vercel) | — |
apidev.d-edu.site |
Dev 백엔드 (온프레) | Cloudflare 터널 → 온프레 서버 |
- 쿠키/CORS: 프론트(
dev.d-edu.site) ↔ API(apidev.d-edu.site)가 형제 서브도메인이라 로그인 쿠키 도메인은.d-edu.site+SameSite=None; Secure. 프론트는dev.d-edu.site로 접속해야 쿠키가 붙음(raw*.vercel.app직접 접속 시 안 붙음).Secure=HTTPS 전송,HttpOnly=JS 접근 차단(XSS),SameSite=교차 사이트 전송 허용 범위 — 형제 서브도메인 인증엔None이 필요하므로 반드시Secure와 함께. 로그인 페이지는Cache-Control: no-store로 이전 사용자 캐시 노출 방지. - Vercel 프리뷰 URL: 브랜치별 동적(
the-edu-front-git-{branch}-idealstudys-projects.vercel.app)이라 하드코딩 불가 → staging/dev 프로파일의cors.allowed-origins·auth.frontend.allowed-origins에 와일드카드https://the-edu-front-git-*-idealstudys-projects.vercel.app허용(팀 네임스페이스 한정 — 임의*.vercel.app금지, prod 제외).WebConfig.setAllowedOriginPatterns+OriginResolver.isValidOrigin이*패턴 지원.
2-5. 환경 · 프로파일 · 시크릿¶
Spring 프로파일 (⚠️ "develop = dev 프로파일"이 아니다): local · staging(온프레 Dev) · dev(구 AWS, 비활성 잔재) · prod(운영). 활성 프로파일은 이미지가 아니라 배포 워크플로의 -e SPRING_PROFILES_ACTIVE로 주입. application-staging.yaml ≈ application-dev.yaml이나 OAuth redirect-uri 호스트가 다름(staging=apidev.d-edu.site) — 온프레에 반영되는 건 staging.
Secrets(변수명만, 값은 GitHub Secrets/raw): 환경별(dev/staging/prod) 분리. 공통 EMAIL_*·DB_ADMIN_*·KAKAO_*, 환경별 DB_URL/USER/PASSWORD·JWT_SECRET·REDIS_*·SENS_*·NAVER_*, AI 코치 AI_PROVIDER·OPENAI_API_KEY.
새 시크릿 추가 절차 (active 경로 모두 주입 필수):
| 경로 | 파일 | 주입 위치 |
|---|---|---|
| AWS (staging/prod, dev 비활성) | back-deploy-aws.yml |
각 docker run 블록 3곳 모두 |
| 온프레 | back-deploy-onprem.yml |
runner docker run 블록 |
| Jenkins | Jenkinsfile |
① environment {} + ② .env heredoc 두 곳 |
| ~~CodeDeploy~~ | scripts/start.sh·appspec.yml |
미사용 — 갱신 대상 아님 |
절차: Secrets 등록 → 위 경로에 -e VAR='${{ secrets.VAR }}' 추가 → application-*.yml에서 ${VAR} 바인딩 → 배포 후 docker inspect|grep VAR 검증.
접근(구조만): Dev 서버=Cloudflare 터널 SSH(공인 IP 없음), Dev DB mysql_dev는 127.0.0.1:3306 바인딩 → SSH 터널로 접속. Prod=SSH 키(.pem, Google Drive 관리)+퍼블릭 IP. 계정·IAM·prod docker 내부 구조는 [SENSITIVE] raw 격리.
2-6. DB 마이그레이션 (Flyway) + 스키마¶
Flyway로 스키마 버전 관리, Spring Boot 시작 시 자동 적용, flyway_schema_history로 이력 추적.
- 네이밍
V{버전}__{설명}.sql(스키마 V1~V999, 시드 V1000~), 디렉토리db/migration/+db/seed/{local,dev}/. - 환경별 검증: Local·Dev(
flywayInfo, 실패해도 진행) / Prod(flywayValidate— 실패 시 배포 중단). PROD는 Hibernateddl-auto: none— DDL은 Flyway로만. - 규칙: 한 마이그레이션=한 목적, 롤백 미지원(새
V{n}__rollback_*), 적용된 파일 수정 금지(체크섬 불일치→validate 실패).
DB 스키마 ↔ 도메인 (운영 DDL). MySQL 8(utf8mb4_0900_ai_ci). 스키마 모양만 보존(자격증명 없음). ERD: theEdu (erdcloud).
| 테이블 | 도메인 | PK / 핵심 컬럼 | FK · 제약 |
|---|---|---|---|
member |
회원 도메인 (Member) | member_id, email UNIQUE, role native enum |
teacher·student가 @MapsId로 PK 공유(조인 전략) |
connection |
회원 도메인 (Member) | id, state enum, accepted_date |
requester_id·recipient_id → member |
study_room |
스터디룸 도메인 (StudyRoom) | id, visibility/modality/subject_type = varchar(50), capacity |
teacher_id → member |
study_room_student |
스터디룸 도메인 (StudyRoom) | id, state enum |
student_id·study_room_id → 각 부모 |
teaching_note_group |
수업노트 도메인 (TeachingNote) | id, title |
study_room_id → study_room ON DELETE CASCADE |
teaching_note |
수업노트 도메인 (TeachingNote) | id, visibility native enum 6단계, view_count |
study_room_id·teaching_note_group_id (후자 ON DELETE SET NULL) |
notification |
(Notification) | id, type enum, is_read, target_id |
recipient_id → member |
- 공통: 모든 테이블
reg_date/mod_date(BaseEntity AUDIT). 소프트 딜리트deleted_at은 도메인 코드 레벨(backend-architecture §4). - FK는 실제로 건다(JPA
@ManyToOne). hard delete용 DBON DELETE CASCADE/SET NULL은 위 두 곳만, soft delete 연쇄는 앱이 처리(engineering-principles). - enum 저장 혼재:
member.role·teaching_note.visibility=native enum,study_room.visibility등=varchar(50)(신규는 varchar 권장).
2-7. 관측성 (모니터링·로깅)¶
Dev/Prod 동일 아키텍처. 메트릭=Prometheus(pull), 로그=Loki(push, Promtail), 시각화=Grafana.
[메트릭] Actuator(Spring) / Node Exporter(호스트) ──pull── Prometheus
[로그] 앱 로그 ──push(Promtail)── Loki(라벨 기반 인덱스) ──→ Grafana 시각화
- Prometheus
name{labels} value ts, Loki는 전체 인덱싱 X·Label 기반만(성능). Actuator 커스텀(url 라벨 제거로 카디널리티 폭증 방지)+로그 MDC 필터, Micrometer로 JVM·CPU·HTTP·커넥션 수집. - Grafana: 컨테이너·서버 관측성 대시보드, P99/P95, RequestID 단위 로그 추적. 컨테이너(Prod)
dedu-grafana(:3000)·dedu-loki(:3100). Grafana admin 등 시크릿은 raw에만. - 데이터 분석: GA4 이벤트 기반. 대규모 스트림/메시지 큐도 검토 대상.
2-8. 알려진 리스크 · TODO (ADR-0017 §Consequences)¶
theedu_backend네트워크 단일 소유자 부재 — 앱은docker run, mysql/redis는 compose라 주인이 없음. 재생성 시 통신 끊긴 적 있음(장애 1회 incidents) → 최상위 compose 일원화.- 온프레 MySQL 자동 백업 없음 — 볼륨 1곳에만 → 정기
mysqldump+ off-box(S3). - 모니터링·알림 부재 — apidev 다운 인지 불가 → Prometheus/Loki/Grafana 재구축.
- deploy 롤백 — 헬스 실패 시 알림+중단이 이전 이미지 롤백보다 나음.
back-deploy-aws.yml의 deploy-dev job 잔존 — dev EC2 삭제로 develop 머지 시 실패 → 비활성화 필요.
2-9. AWS → 온프레미스 이전 (왜 · 어떻게 · 교훈)¶
왜 옮겼나. 기존 Dev 백엔드는 AWS였다 — EC2 프리티어 + RDS MySQL the_edu + ECR, 도메인 api.dev.d-edu.site. 프리티어가 만료되며 EC2/RDS 상시 과금이 시작됐고, IAM·VPC를 직접 손보기에도 제약이 있었다. 마침 사무실에 상시 가동 서버가 있어, Dev를 온프레로 내리면 클라우드 비용을 0으로 만들고 인프라·데이터를 직접 쥘 수 있었다. 운영(Prod)은 안정성 때문에 AWS Lightsail로 그대로 뒀다.
어떻게 옮겼나 (2026-05~06):
- 백엔드 → 온프레 Docker:
the-edu-staging(Spring) +mysql_dev(MySQL 8.4) +theedu_redis를theedu_backend도커 네트워크로 묶고, 서버에 공인 IP/포트를 열지 않은 채 Cloudflare 터널로apidev.d-edu.site·ssh.d-edu.site를 노출했다. 배포는 사무실 서버에 둔 self-hosted GitHub Actions runner가 돌린다. - 프론트 → Vercel: 모노레포를 직접 붙이지 않고 public 미러(
the-edu-project)를 거쳐 자동 배포로 전환(§2-2). - 데이터 이관: AWS RDS
the_edu→mysqldump→ 온프레mysql_dev(DB명dedu). member 266건·study_room 416건 이관·검증. - AWS S3
theedu는 유지: 온프레 백엔드도 미디어 presigned URL에 AWS 키를 계속 쓴다(그래서 deploy에AWS_*주입). - 검증 후 dev EC2/RDS 삭제 — 되돌릴 때를 대비해 최종 스냅샷
dev-onpremise-260531을 보관.
무엇을 배웠나(교훈). 비용은 없앴지만, 관리형 클라우드(AWS)가 공짜로 주던 것들을 직접 떠안게 됐다 — 그 빚이 곧 후속 과제(§2-8)다:
- 네트워크 주인이 없어 한 번 터졌다. 앱은
docker run, DB/Redis는compose로 따로 띄워theedu_backend네트워크의 소유자가 모호했고, 네트워크를 재생성하자 컨테이너 간 통신이 끊겨 dev 백엔드가 502를 뱉었다(incidents 2026-05-31). → 최상위 compose로 일원화가 필요하다. - 자동 백업이 사라졌다. RDS 자동백업 대신 데이터가 도커 볼륨 한 곳에만 남는다 → 정기
mysqldump+ off-box(S3) 백업이 숙제. - 다운을 알 길이 없다. 관측·알림이 없어
apidev가 죽어도 모른다 → Prometheus/Loki/Grafana 재구축(§2-7).
대안 검토(프리티어 2계정 / Dev도 Lightsail / 프론트도 온프레)와 결정의 전체 근거·기록은 ADR-0017에 있다(이 절은 그 결정을 본문으로 풀어 쓴 것).
구 AWS Dev 구성 (삭제됨, 스냅샷 보존):
| 항목 | 값 |
|---|---|
| 컴퓨팅 | EC2 프리티어 |
| DB | RDS MySQL the_edu |
| 레지스트리 | ECR (theedu/back) |
| 백엔드 도메인 | api.dev.d-edu.site (nginx + certbot) |
| 배포 | deploy.yml deploy-dev → SSH로 EC2 docker 교체 |
구 Prod 프론트: 과거엔 Lightsail이 nginx+certbot으로 React 정적 빌드까지 서빙했다(SSR 대신 정적 빌드로 간 배경은 tech-decisions). 프론트가 Vercel로 이전되며 Lightsail은 백엔드 전용이 됐다.
3. ② 위키 시스템 (팀·LLM이 지식을 쌓는 것)¶
wiki/ 마크다운이 정본이고, 세 갈래로 소비된다 — ① 정적 사이트, ② RAG 검색, ③ Slack→PR 자동화. 전체를 LLM 하네스(CLAUDE.md·hooks·skills)가 규율한다.
flowchart LR
WC["wiki/ 마크다운<br/>(5축 v12 정본)"]
subgraph BUILD["정적 사이트"]
MK["MkDocs Material<br/>+ wikilinks.py"]
PG["Cloudflare Pages<br/>wiki.d-edu.site"]
MK --> PG
end
subgraph SEARCH["AI 검색 (AutoRAG)"]
R2[("R2: wiki/ 콘텐츠")]
AR["dedu-wiki<br/>bge-m3 + 생성모델"]
WK["Worker: dedu-wiki-rag"]
R2 --> AR --> WK
end
subgraph PIPE["자동화 (Slack→PR)"]
S1["stage1 Slack→raw+Issue"] --> S2["stage2 Issue→PR"] --> S3["stage3 merge→changelog"]
end
SRC["Slack 멘션 / Notion 확정본 / raw"] --> S1
S2 --> WC
WC --> MK
WC --> R2
PG -. ✨AI검색 위젯 .-> WK
정적 사이트: 코드와 같은 모노레포라 위키 .md 수정 = 사이트 콘텐츠 수정(별도 CMS 없음).
| 항목 | 값 |
|---|---|
| 빌더 | MkDocs Material — mkdocs.yml(docs_dir: wiki → site_dir: site) |
| 호스팅 | Cloudflare Pages 프로젝트 mvp-mono(git 연동) → mvp-mono.pages.dev → wiki.d-edu.site |
| 빌드 | pip install -r requirements.txt && mkdocs build |
| 배포 트리거 | develop 머지 시 CF Pages 자동 재빌드(production 브랜치 = develop) |
wikilink 변환 |
훅 .mkdocs/wikilinks.py(경로→제목 치환·basename·ADR 단축형) |
| 빌드 가드 | wiki-web-check.yml — wiki PR에서 mkdocs build --strict 선검증 |
wiki 변경 PR → develop 머지 → 1~2분 내 사이트 반영. 별도 배포 워크플로 불필요(CF Pages가 직접 빌드).
AI 검색 (AutoRAG — 별개 파이프라인): 사이트 검색 위젯 → dedu-wiki-rag Worker(services/wiki-rag) → Cloudflare AI Search 인스턴스 dedu-wiki aiSearch() → 답변+출처. ⚠️ 재색인은 CF 대시보드 설정 — 정적 사이트 자동배포와 분리되어 있어, 위키가 바뀌어도 재색인 안 하면 검색만 stale.
자동화 (Slack → wiki PR): Slack 멘션 → CF Worker wiki-bot(services/wiki-bot) → GitHub repository_dispatch → wiki-stage1/2/3.yml(raw 박제→Issue→wiki:컴파일 라벨→Claude가 PR→merge→changelog). 라벨·시크릿·흐름: ADR-0014·ADR-0015.
조회수 집계(
services/wiki-viewsWorker+D1)도 이 사이트에 붙어 popular 리포트를 만든다.
4. ③ 마케팅 에이전트 (유입을 만드는 것)¶
학생 유입을 만드는 콘텐츠 자동화 시스템. 모노레포와 분리된 별도 레포다.
flowchart LR
GEN["① 생성<br/>Claude/OpenAI"] --> REV["② 사람 검수 게이트"] --> PUB["③ 채널 발행"] --> ANL["④ 성과 분석"] --> GEN
PUB --> CH["SNS 채널<br/>릴스·카드뉴스·오픈챗"] --> U["학생 유입"]
- 레포:
idealstudy/marketing-AI(public, TypeScript). 모노레포 흡수 안 함(마케팅 전용·결합도 낮음 — 의도된 결정). - 파이프라인: 생성(Claude/OpenAI) → 사람 검수 → 채널별 자동 발행 → 성과 트래킹(OpenClaw 기반).
- 대시보드:
openclaw-dashboard.d-edu.site(Cloudflare Tunnel). - 위키 연결: 마케팅 전략·페르소나·캠페인 로그는 위키(
5-hubs/hub-mkt)에, 실행 자산은assets/에. 운영 가이드·콘텐츠 전략은 콘텐츠 자동화 (AI 파이프라인)·channel-playbooks.
관련¶
backend-architecture · frontend-architecture · version-control · version-control · tech-decisions · ADR-0017 · ADR-0003 · ADR-0014 · incidents