generated from ttipo/project-base
238d0ba838
웹메일 1단계를 개발 환경에서 띄우고 운영에 올리기 위한 인프라 구성이다. - 메일 엔진 Stalwart(v0.16.27)와 개발용 Mailpit 을 compose profile `mail` 로 추가한다. 초기 설정은 mail/scripts/setup.mjs 가 맡는다 — 여러 번 실행해도 안전하고, 운영에서는 사전 검사가 실패하면 아무것도 바꾸지 않고 멈춘다 - 화면과 API 를 한 주소로 배포한다: 프론트 nginx 가 /api 를 백엔드로 넘기고, 본문 한도·시간 제한·헤더 전달을 env 와 맞춘다. nginx 가 직접 내는 오류도 표준 실패 형식이다 - 인터넷을 향한 nginx 는 edge 네트워크로 백엔드에만 닿고, DB·메일 엔진은 app-network 에 둔다 (메일 엔진의 신뢰 대역도 app-network 뿐) - Node 24.22.1·npm 11.6.2·nginx 1.30.6 을 Dockerfile 과 CI 에 한 값으로 고정한다 - 개발 호스트 포트는 127.0.0.1 로 한정하고, data/ 는 클론 때부터 사용자 소유가 되게 .gitkeep 을 추적한다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
166 lines
10 KiB
Bash
166 lines
10 KiB
Bash
# ==========================================
|
|
# 프로젝트 전역 설정
|
|
# ==========================================
|
|
PROJECT_NAME=project
|
|
APP_ENV=production
|
|
BUILD_MODE=production
|
|
FRONTEND_DIR=frontend
|
|
BACKEND_DIR=backend
|
|
|
|
# ==========================================
|
|
# 포트 설정
|
|
# ==========================================
|
|
FRONTEND_PORT=80
|
|
BACKEND_PORT=3000
|
|
WEBSOCKET_PORT=3001
|
|
DB_PORT=5432
|
|
REDIS_PORT=6379
|
|
|
|
# ==========================================
|
|
# 내부 네트워크 (compose app-network · edge)
|
|
#
|
|
# 서브넷을 고정한다 — 메일 엔진은 app-network 대역만 신뢰한다 (게이트웨이 로그인·자동 차단 예외, mail/README.md).
|
|
# APP_NETWORK_SUBNET : backend·db·redis·mail — 사설 대역, /24 ~ /28 (mail setup 이 검사한다)
|
|
# EDGE_NETWORK_SUBNET : frontend(nginx) ↔ backend 전용. 인터넷을 향한 frontend 는 app-network 에 붙지 않는다.
|
|
# TRUST_PROXY 를 주소 목록으로 쓸 때 이 대역을 넣는다 (아래)
|
|
# 도커 기본 주소 풀(172.17~31.x, 192.168.x) 밖의 값이라 자동으로 배정되는 네트워크와 겹치지 않는다.
|
|
# 같은 호스트의 다른 프로젝트와 겹치면 up 이 "Pool overlaps" 로 실패한다 → 다른 /24 로 바꾸고 setup 을 다시 실행한다.
|
|
# ==========================================
|
|
APP_NETWORK_SUBNET=172.16.71.0/24
|
|
EDGE_NETWORK_SUBNET=172.16.72.0/24
|
|
|
|
# ==========================================
|
|
# Database 설정
|
|
# ==========================================
|
|
DB_IMAGE=postgres:15-alpine
|
|
DB_USER=app_user
|
|
DB_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
|
|
DB_NAME=project_db
|
|
DB_DATA_DIR=./data/postgres
|
|
|
|
# ==========================================
|
|
# Redis 설정
|
|
# ==========================================
|
|
REDIS_IMAGE=redis:7-alpine
|
|
REDIS_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
|
|
REDIS_DATA_DIR=./data/redis
|
|
|
|
# ==========================================
|
|
# 화면·API 주소 — 같은 주소(origin) 배포 (docs/mail/api-contract.md 머리말)
|
|
#
|
|
# 브라우저는 포털 주소의 /api 를 부르고 프론트 nginx 가 백엔드로 넘긴다. 운영의 입구는 프론트 nginx 하나다.
|
|
# - CORS_ORIGIN : 화면의 공개 주소(origin, 여러 개면 콤마). 상태 변경 요청의 출처 검사에 쓴다 — 운영 필수
|
|
# - BACKEND_API_URL : 화면 주소 + /api. 앱은 쓰지 않고, 운영 기동 때 공개 주소인지 검사하는 데만 쓴다
|
|
#
|
|
# localhost 나 example.com 이 남아 있으면 **백엔드가 기동에 실패한다**
|
|
# (backend/src/shared/config/env.validation.ts — fail-fast).
|
|
# 로컬에서 운영 이미지를 검증하느라 일부러 localhost 를 쓰는 경우에만
|
|
# ALLOW_LOCALHOST_URL=true 로 이 검사를 건너뛴다. 실제 배포에는 설정하지 않는다.
|
|
#
|
|
# - TRUST_PROXY : 백엔드 앞 프록시 — 실제 클라이언트 IP(로그인 실패 한도의 기준)를 읽는다. 운영 필수
|
|
# 이 compose 의 운영 경로는 클라이언트 → 외부 리버스 프록시(TLS) → 프론트 nginx → 백엔드 하나뿐이다.
|
|
# (프론트 nginx 는 호스트 포트를 열지 않고 TLS 도 하지 않으므로 외부 프록시 없이는 들어올 수 없다 — 1 은 해당 없음)
|
|
# 2 = 홉 수. proxy 네트워크를 이 프로젝트의 외부 프록시만 쓸 때
|
|
# 주소 목록 = <EDGE_NETWORK_SUBNET>,<외부 프록시 IP> (예: 172.16.72.0/24,172.20.0.10) — proxy 네트워크를
|
|
# 다른 프로젝트와 함께 쓴다면 이쪽을 권장한다. 홉 수는 "몇 번째 주소"만 믿어서, 같은 네트워크의 다른
|
|
# 컨테이너가 외부 프록시를 건너뛰고 frontend 에 바로 붙으면 X-Forwarded-For 를 꾸며 IP 를 바꿀 수 있다.
|
|
# 외부 프록시 IP 는 frontend 가 보는 주소(proxy 네트워크 안 주소)이며 고정해 둔다(ipv4_address 등).
|
|
# 넓은 사설 대역을 통째로 넣지 않는다.
|
|
# true·0·false 는 운영에서 기동이 거부된다.
|
|
# ==========================================
|
|
BACKEND_API_URL=https://portal.arasystem.it.kr/api
|
|
CORS_ORIGIN=https://portal.arasystem.it.kr
|
|
TRUST_PROXY=2
|
|
|
|
# ==========================================
|
|
# Rate Limiting (API 남용 방지)
|
|
# 미설정 시 백엔드 기본값(60초 / 100회) 사용
|
|
# ==========================================
|
|
THROTTLE_TTL_MS=60000
|
|
THROTTLE_LIMIT=100
|
|
|
|
# ==========================================
|
|
# DB 스키마 동기화 (개발 전용, 기본 false)
|
|
# true 로 켜면 엔티티 변경이 DB 에 자동 반영되어 빠르게 실험할 수 있지만,
|
|
# 그 상태에서는 migration:generate 가 "변경 없음"만 내놓는다.
|
|
# 운영으로 갈 코드라면 꺼둔 채 마이그레이션으로 관리할 것.
|
|
# (운영 환경에서는 이 값과 무관하게 항상 비활성화된다)
|
|
# ==========================================
|
|
DB_SYNCHRONIZE=false
|
|
|
|
# ==========================================
|
|
# 선택적 서비스 (Compose profiles)
|
|
#
|
|
# 기본값 = mail — 메일 엔진(Stalwart)을 띄운다. 직원 로그인이 메일 엔진에 의존한다.
|
|
# (redis 는 기동하지 않는다)
|
|
# 캐시·세션 공유가 필요해지면 redis 를 추가하면 컨테이너가 함께 뜬다.
|
|
# COMPOSE_PROFILES=mail,redis
|
|
# 일회성으로 켜려면: docker compose --profile redis ... up -d
|
|
# ==========================================
|
|
COMPOSE_PROFILES=mail
|
|
|
|
# ==========================================
|
|
# 파일 업로드 (로컬 디스크)
|
|
#
|
|
# 저장 위치는 named volume <PROJECT_NAME>_uploads 다 (docker-compose.yml). 호스트 디렉터리를 쓰지 않는다 —
|
|
# sudo docker compose 가 없는 디렉터리를 root 소유로 만들어 backend(node)가 쓰지 못한다.
|
|
# (UPLOAD_DATA_DIR 는 개발 전용이라 운영에는 두지 않는다. 백업은 docker 스킬 "운영 환경")
|
|
# ==========================================
|
|
UPLOAD_MAX_SIZE_MB=5
|
|
|
|
# ==========================================
|
|
# 메일 엔진 (Stalwart) — profile `mail`
|
|
#
|
|
# 운영 기본 가정 = 메일 전용 호스트: 25·465·993·443 을 이 호스트에 직접 연다 (docker-compose.yml).
|
|
# 첫 배포 후 setup 스크립트를 한 번 실행한다 — mail/README.md "운영 배포".
|
|
#
|
|
# ⚠️ 비밀번호는 모두 강한 무작위 값으로 교체한다. CHANGE-ME 가 남아 있으면 setup 스크립트가 중단한다.
|
|
# 영문·숫자로만 만든다 — # · 따옴표 · $ · 공백은 compose 와 Node 가 다르게 읽는다.
|
|
# 예: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
|
|
# ⚠️ 8080(관리 API)은 공개하지 않는다. setup 스크립트는 내부망에서 실행한다 (README 참조).
|
|
# 공개 443 의 JMAP·관리 API·웹 포털은 setup 이 막는다 (MTA-STS 정책 파일만 열어 둔다).
|
|
# ==========================================
|
|
# 패치까지 고정한다 — setup 스크립트가 0.16 의 동작(관리 API·bootstrap)에 맞춰져 있다 (docker 스킬 "이미지 버전 고정")
|
|
MAIL_IMAGE=stalwartlabs/stalwart:v0.16.27
|
|
MAIL_DOMAIN=arasystem.it.kr
|
|
# 새 엔진의 호스트명 (EHLO·PTR·TLS 인증서) — 최초 bootstrap 때만 반영된다.
|
|
# mailcow 가 mail.arasystem.it.kr 를 쓰는 전환 기간에는 새 이름(mx1.)을 쓴다 (mail/README.md "전환 절차")
|
|
MAIL_HOSTNAME=mx1.arasystem.it.kr
|
|
# JMAP·OAuth 디스커버리 기준 주소 — 새 엔진에 실제로 닿는 주소여야 한다
|
|
MAIL_PUBLIC_URL=https://mx1.arasystem.it.kr
|
|
# 영구 관리자 아이디 (주소는 <이 값>@MAIL_DOMAIN, 미설정 시 admin) — setup 스크립트가 이 이름으로 관리자를 만들고,
|
|
# 백엔드는 이 계정의 웹메일 로그인·대리 접근을 막는다 (두 쪽이 이 값 하나를 읽는다).
|
|
# ⚠️ 운영 bootstrap 전에 정한다. mailcow 에 실제 admin@ 메일함이 있으면 다른 이름으로 한다
|
|
# (그대로 두면 엔진 관리자와 주소가 겹쳐 그 메일함은 옮겨 와도 웹메일에 로그인할 수 없다).
|
|
# ⚠️ bootstrap 뒤에 이 값만 바꾸면 엔진 관리자는 그대로 남고 백엔드는 새 이름만 막게 되므로, setup 이 중단한다
|
|
# (되돌리는 절차: mail/README.md "문제 해결"). 영문·숫자·. _ - 만, 앞뒤는 영문·숫자, 64자 이하 (백엔드와 같은 규칙)
|
|
MAIL_ADMIN_LOCAL_PART=admin
|
|
# 영구 관리자 비밀번호 — setup 스크립트가 이 값으로 맞춘다
|
|
MAIL_ADMIN_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
|
|
# 복구 관리자(MAIL_RECOVERY_ADMIN, "아이디:비밀번호")는 이 파일에 적지 않는다 — 최초 bootstrap·관리자 복구 때
|
|
# 셸에서 일회성으로만 준다. 값이 있는 동안은 정상 모드에서도 관리자로 로그인되기 때문이다.
|
|
# 이 파일에 값이 있거나, 끝난 뒤에도 mail 컨테이너에 남아 있으면 setup 이 실패한다 (mail/README.md "운영 배포").
|
|
# read -rsp '임시 복구 비밀번호: ' P; echo; export MAIL_RECOVERY_ADMIN="admin:$P"
|
|
# docker compose --env-file .env.production up -d mail
|
|
# node --env-file=.env.production mail/scripts/setup.mjs --recovery
|
|
# unset MAIL_RECOVERY_ADMIN P; docker compose --env-file .env.production up -d mail # 복구 관리자 끄기
|
|
# node --env-file=.env.production mail/scripts/setup.mjs # 꺼졌는지 확인
|
|
# 백엔드가 직원 메일함을 대리 접근(impersonate)할 때 쓰는 서비스 계정 (주소 전체).
|
|
# ⚠️ 이 비밀번호는 전 직원 메일함의 열쇠다
|
|
MAIL_GATEWAY_USER=mail-gateway@arasystem.it.kr
|
|
MAIL_GATEWAY_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
|
|
# 백엔드가 쓰는 내부 JMAP 주소 (compose 내부망)
|
|
MAIL_JMAP_URL=http://mail:8080
|
|
# 첨부 1개 최대 크기(MB, 미설정 시 20). 프론트 nginx 의 본문 한도는 이 값 + 1MB 로 자동으로 따라간다.
|
|
# ⚠️ 그 앞의 외부 리버스 프록시 본문 한도는 직접 이 값 + 1MB 이상으로 둔다 —
|
|
# 작으면 프록시가 먼저 413 을 내고 사용자에게는 FILE_002 로 보인다 (docker 스킬 "운영 환경")
|
|
MAIL_ATTACHMENT_MAX_SIZE_MB=20
|
|
# 메일 한 통의 첨부 합계(MB, 미설정 시 25) — 파일당 한도 이상이어야 한다
|
|
MAIL_ATTACHMENT_TOTAL_MAX_SIZE_MB=25
|
|
# 게이트웨이 계정을 쓸 수 있고 자동 차단에서 빠지는 대역 (콤마 구분).
|
|
# 비우면 APP_NETWORK_SUBNET 과 같다. 그 안의 더 좁은 대역만 쓸 수 있다 — 더 넓으면 setup 이 실패한다
|
|
MAIL_TRUSTED_NETWORKS=
|
|
# (선택) 웹메일 로그인·대리 접근을 막을 계정 — 주소 전체를 콤마로. 비어 있어도 위 관리자(MAIL_ADMIN_LOCAL_PART)는 항상 막힌다.
|
|
# 관리자를 더 만들었다면 여기에 넣는다 (setup 이 빠진 관리자 계정을 [주의] 로 알려 준다)
|
|
# MAIL_BLOCKED_ACCOUNTS=ops@arasystem.it.kr
|