docs(rules): 메일 전용 리졸버 · Spamhaus 키 — docker 스킬 · env 규칙
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 11m38s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m22s
CI / Infra (compose · Dockerfile · nginx) (pull_request) Successful in 37s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m33s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 12m50s
CI / Frontend (lint · type-check · test · build) (push) Successful in 17m42s
CI / Infra (compose · Dockerfile · nginx) (push) Successful in 41s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 10m31s

docker 스킬에 리졸버 네트워크 두 개 · 이미지 고정(digest) · 비-root · CI 설정 검사 ·
장애와 재부팅 때 할 일을, env 규칙에 MAIL_RESOLVER_SUBNET/IP 와 setup 전용 비밀
MAIL_SPAMHAUS_DQS_KEY(운영은 파일에만)를 더했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #6.
This commit is contained in:
2026-10-30 19:01:13 +09:00
parent 15141e7723
commit c3dba24be0
2 changed files with 33 additions and 6 deletions
+8 -2
View File
@@ -22,6 +22,8 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{
│ vite.config.ts 가 이 파일을 `loadEnv` 로 직접 읽어 FRONTEND_PORT/BACKEND_PORT 사용)
├─▶ network : APP_NETWORK_SUBNET → app-network 서브넷 고정 (메일 엔진의 신뢰 대역 — 사설 대역 /24~/28, frontend 는 붙지 않는다)
│ EDGE_NETWORK_SUBNET → edge(frontend ↔ backend) 서브넷 고정 (TRUST_PROXY 주소 목록용)
│ MAIL_RESOLVER_SUBNET · MAIL_RESOLVER_IP → mail-dns(mail ↔ 메일 전용 DNS 리졸버, internal) 서브넷 · 리졸버 주소 고정
│ (mail 의 dns · 엔진의 DNS 가 이 IP 를 쓴다 — 둘을 함께 바꾸고 setup 을 다시 실행한다)
├─▶ db : DB_USER/DB_PASSWORD/DB_NAME, DB_DATA_DIR 볼륨
├─▶ redis : REDIS_PASSWORD, REDIS_DATA_DIR 볼륨
├─▶ uploads : 운영은 named volume <PROJECT_NAME>_uploads (env 없음), 개발만 UPLOAD_DATA_DIR 바인드 마운트
@@ -33,6 +35,8 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{
└─▶ mail/scripts/setup.mjs : `node --env-file=.env.{APP_ENV}` 로 같은 파일을 직접 읽는다 (mail/README.md)
MAIL_ATTACHMENT_*(첨부 한도)도 읽는다 — 엔진의 임시 업로드 한도를 그 20배로 맞추므로 첨부 한도를 바꾸면 setup 도 다시 돌린다
MAIL_PILOT_LEGACY_HOST · MAIL_PILOT_LEGACY_IPS(10단계 파일럿 공존 — 옛 서버 주소 · 그 서버가 보내는 IP)는 setup 만 읽는다(compose · backend 에 넘기지 않는다)
MAIL_SPAMHAUS_DQS_KEY(Spamhaus 조회 키 — 비밀)도 setup 만 읽는다. 운영은 .env.production 에만(셸에만 있으면 setup 이 멈춘다),
개발은 비워 둔다 — 비면 Spamhaus 두 항목을 끈다. 키는 엔진의 스팸 필터 설정에 그대로 저장된다 (mail/README.md "DNS 리졸버와 스팸 차단 목록")
```
규칙:
@@ -58,6 +62,7 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{
스크립트는 env 파일 값과 셸 값이 다르면 중단해야 한다 (`mail/scripts/setup.mjs` 의 `findShellOverrides()`).
예외는 복구 관리자 `MAIL_RECOVERY_ADMIN`(원래 셸에서 일회성으로 주는 값이라 검사하지 않는다)과 파일럿 키 둘이다 — 파일럿 키는 개발은 한 번 시험하려고 셸로 줄 수 있고(`[참고]` 를 낸다),
운영은 반대로 파일에 없는 파일럿 값이 셸에 있으면 중단한다 (파일럿은 엔진에 남는 설정이라 파일만 보고도 알 수 있어야 한다). 운영에서 파일럿을 끄는 것은 값을 비우고 `--end-pilot` 을 줄 때뿐이다 — 값이 빠졌다고 저절로 끄지 않는다.
Spamhaus 키 `MAIL_SPAMHAUS_DQS_KEY` 도 운영은 파일에만 둔다 — 셸에만 있으면 그 실행만 켜지고 다음 실행이 끄므로 중단한다.
- Node 는 따옴표 없는 값의 `#` 부터를 **공백이 없어도** 주석으로 자르고, compose 는 그대로 읽는다.
비밀번호 등은 영문·숫자로만 만든다 (`#`·따옴표·`$`·공백 금지).
- 모든 env 파일은 커밋 금지, `*.example` 만 커밋한다. (`.gitignore` 의 `!*.example` 재포함 규칙)
@@ -74,12 +79,13 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{
- 문제는 하나씩이 아니라 **한 번에 모아서** 보여준다 (재기동 반복 방지)
- 새 필수 설정을 추가하면 이 파일의 목록과 테스트도 함께 갱신한다
> 루트 env의 대표 키: `PROJECT_NAME`, `APP_ENV`, `BUILD_MODE`, `COMPOSE_PROFILES`·`COMPOSE_FILE`(운영의 remote-proxy 일 때), 각종 `*_PORT`, `APP_NETWORK_SUBNET`·`EDGE_NETWORK_SUBNET`, `BACKEND_API_URL`, `CORS_ORIGIN`, `TRUST_PROXY`, `FRONTEND_PRIVATE_BIND`(remote-proxy 일 때),
> 루트 env의 대표 키: `PROJECT_NAME`, `APP_ENV`, `BUILD_MODE`, `COMPOSE_PROFILES`·`COMPOSE_FILE`(운영의 remote-proxy 일 때), 각종 `*_PORT`, `APP_NETWORK_SUBNET`·`EDGE_NETWORK_SUBNET`·`MAIL_RESOLVER_SUBNET`/`MAIL_RESOLVER_IP`, `BACKEND_API_URL`, `CORS_ORIGIN`, `TRUST_PROXY`, `FRONTEND_PRIVATE_BIND`(remote-proxy 일 때),
> `DB_*`/`DB_DATA_DIR`, `REDIS_*`/`REDIS_DATA_DIR`, `THROTTLE_TTL_MS`/`THROTTLE_LIMIT`, `UPLOAD_MAX_SIZE_MB`(·개발 전용 `UPLOAD_DATA_DIR`),
> 메일 `MAIL_IMAGE`(패치까지 고정)·`MAIL_DOMAIN`·`MAIL_HOSTNAME`·`MAIL_PUBLIC_URL`·`MAIL_ADMIN_LOCAL_PART`(관리자 아이디, 기본 `admin` — setup·backend 공용, bootstrap 전에만)·
> `MAIL_ADMIN_PASSWORD`·`MAIL_RECOVERY_ADMIN`(운영은 일회성 override)·`MAIL_GATEWAY_USER`/`MAIL_GATEWAY_PASSWORD`·`MAIL_DIRECTORY_LOCAL_PART`(사내 목록 계정 아이디, 기본 `mail-directory` — setup·backend 공용)·
> `MAIL_JMAP_URL`·`MAIL_ATTACHMENT_MAX_SIZE_MB`/`MAIL_ATTACHMENT_TOTAL_MAX_SIZE_MB`·`MAIL_TRUSTED_NETWORKS`(비우면 `APP_NETWORK_SUBNET`, 그보다 넓으면 setup 실패)·
> `MAIL_BLOCKED_ACCOUNTS`(선택 — 관리자 외에 웹메일 로그인·대리 접근을 막을 주소, 콤마. 비어 있어도 관리자는 항상 막힌다)·
> `MAIL_PILOT_LEGACY_HOST`/`MAIL_PILOT_LEGACY_IPS`(선택 — 파일럿 공존 동안만, setup 전용. mail/README.md "파일럿 공존")
> `MAIL_PILOT_LEGACY_HOST`/`MAIL_PILOT_LEGACY_IPS`(선택 — 파일럿 공존 동안만, setup 전용. mail/README.md "파일럿 공존")·
> `MAIL_SPAMHAUS_DQS_KEY`(선택 · 비밀 — Spamhaus 조회 키, setup 전용, 운영 파일에만)
> (개발 전용 `MAIL_ADMIN_PORT`·`MAIL_DEV_USER_PASSWORD`·`MAILPIT_IMAGE`/`MAILPIT_UI_PORT`). 실제 목록은 `.env.development.example` 참조.
> 실행/배포 명령은 [[docker]], 보안 규칙은 [[security]] 참조.