generated from ttipo/project-base
3029bb20f2
운영 메일 서버를 NCP 에 따로 두고, 포털의 TLS 는 기존 서버의 Nginx Proxy Manager(다른 사이트들과 80 · 443 을 함께 쓴다)가
같은 VPC 사설망으로 넘기기로 했다. 메일 엔진이 그 서버의 443 을 직접 써야 해서(ACME · MTA-STS) 프록시를 같은 호스트에 둘 수 없다.
- docker-compose.remote-proxy.yml: frontend 를 proxy 네트워크에서 빼고 8080 을 FRONTEND_PRIVATE_BIND(사설 IPv4:포트)에만 게시한다.
compose 의 ${…:?} 는 빈 값만 막아 포트뿐인 값이면 모든 주소에 열리므로, 한 번짜리 frontend-bind-check 가 값을 먼저 보고
아니면 frontend 를 띄우지 않는다(네트워크 없음 · 비-root · DB 이미지의 셸).
- .env.production 의 COMPOSE_FILE 로 켠다 — 운영 명령(--env-file .env.production)을 그대로 쓰고 -f 를 빠뜨려 기본 구성으로 도는 일이 없게.
- TRUST_PROXY 는 <EDGE_NETWORK_SUBNET>,<프록시 서버 사설 IP> 주소 목록, NPM 은 v2.16.0 소스로 기본값을 확인해 고급 칸 세 줄
(읽기 150s · 접근 로그 끔 · 사설 대역의 X-Real-IP 믿지 않기)과 Block Common Exploits 끄기(메일 검색이 403)를 적었다.
- NCP 는 공인 IP 를 사설 IP 로 NAT 하므로 게시 주소로는 바깥을 막지 못한다 — 8080 은 ACG 가 프록시 서버 사설 IP 에서만 받는다.
- CI infra 잡이 이 조합의 config 와 frontend-bind-check 의 받을 값 · 거절할 값을 본다.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
4.8 KiB
YAML
76 lines
4.8 KiB
YAML
# ==========================================
|
|
# 선택 파일 — 리버스 프록시가 다른 서버에 있을 때 (mail/README.md "리버스 프록시가 다른 서버에 있을 때")
|
|
#
|
|
# 기본(docker-compose.yml 단독)은 외부 리버스 프록시가 같은 호스트에서 external proxy 네트워크로
|
|
# <PROJECT_NAME>-frontend:8080 에 붙는다. 이 파일은 프록시가 다른 서버(예: 같은 VPC 의 Nginx Proxy Manager)에 있어
|
|
# 사설망으로 이 서버의 frontend 에 붙는 구성으로 바꾼다:
|
|
# - frontend 를 proxy 네트워크에서 뺀다 (이 서버에는 그 네트워크가 없다). edge 로만 backend 에 닿는 것은 그대로다
|
|
# - frontend 의 8080 을 이 서버의 사설 IP 에만 게시한다 — FRONTEND_PRIVATE_BIND = <사설 IPv4>:<포트>
|
|
# frontend 의 환경 · nginx 설정(X-Forwarded-For 이어 붙이기 · X-Request-Id · 실시간 알림)은 바꾸지 않는다.
|
|
#
|
|
# 쓰는 법 — 루트 .env.production 에 두 줄 (리눅스는 COMPOSE_FILE 을 : 로 잇는다):
|
|
# COMPOSE_FILE=docker-compose.yml:docker-compose.remote-proxy.yml
|
|
# FRONTEND_PRIVATE_BIND=10.0.1.12:8080
|
|
# compose 가 --env-file 의 COMPOSE_FILE 을 읽으므로 문서의 운영 명령(sudo docker compose --env-file .env.production …)이
|
|
# 그대로 두 파일을 읽는다 (-f 를 붙이지 않는다 — 하나라도 빠뜨리면 그 명령만 기본 구성으로 돈다).
|
|
#
|
|
# ⚠️ 게시 주소는 바깥을 막는 장치가 아니다. NCP 처럼 공인 IP 를 사설 IP 로 NAT 하는 곳에서는 사설 IP 에 게시한 포트에
|
|
# 공인 IP 로도 닿는다 — 8080 은 방화벽(NCP 는 ACG)이 프록시 서버의 사설 IP 에서만 받게 한다.
|
|
# ==========================================
|
|
services:
|
|
frontend:
|
|
# 다른 서버의 프록시가 붙는 자리 — 이 서버의 사설 IPv4 에만 (0.0.0.0 · 빈 값으로 열지 않는다).
|
|
# 값이 없으면 compose 가 멈추고, <사설 IPv4>:<포트> 꼴이 아니면 아래 frontend-bind-check 가 frontend 를 띄우지 않는다
|
|
# (값이 포트뿐이면 "8080:8080" 이 되어 모든 주소에 열리기 때문이다)
|
|
ports: !override
|
|
- "${FRONTEND_PRIVATE_BIND:?FRONTEND_PRIVATE_BIND 가 비어 있습니다 — 이 서버의 사설 IPv4 와 포트 (예 10.0.1.12:8080) · mail/README.md 의 리버스 프록시가 다른 서버에 있을 때}:8080"
|
|
# 이 서버에는 외부 프록시의 proxy 네트워크가 없다 — edge(frontend ↔ backend)만 남긴다.
|
|
# app-network(메일 엔진의 신뢰 대역)에는 기본 구성처럼 붙이지 않는다
|
|
networks: !override
|
|
- edge
|
|
depends_on:
|
|
frontend-bind-check:
|
|
condition: service_completed_successfully
|
|
|
|
# ----------------------------------------
|
|
# 게시 주소 확인 — frontend 보다 먼저 한 번 돌고 끝난다 (docker compose ps -a 에 Exited (0))
|
|
#
|
|
# compose 의 ${…:?} 는 빈 값만 막는다. 포트뿐인 값 · 0.0.0.0 · 공인 IP · IPv6 는 여기서 막는다 — 실패하면 up 이
|
|
# "frontend-bind-check didn't complete successfully" 로 멈추고 frontend 는 뜨지 않는다 (까닭: logs frontend-bind-check).
|
|
# 셸만 쓴다 — 이미 받는 DB 이미지를 빌려 새 이미지 · 버전 고정 자리를 늘리지 않는다. 네트워크 없이 · 비-root · 읽기 전용.
|
|
# 받는 값: RFC 1918 사설 IPv4(10/8 · 172.16/12 · 192.168/16, 앞자리 0 없이) + ":" + 포트 1~65535(앞자리 0 없이)
|
|
# ----------------------------------------
|
|
frontend-bind-check:
|
|
container_name: ${PROJECT_NAME}-frontend-bind-check
|
|
image: ${DB_IMAGE}
|
|
user: "65534:65534"
|
|
network_mode: none
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
restart: "no"
|
|
environment:
|
|
- FRONTEND_PRIVATE_BIND=${FRONTEND_PRIVATE_BIND:-}
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
v=$$FRONTEND_PRIVATE_BIND
|
|
o='(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])'
|
|
p='([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])'
|
|
case $$v in
|
|
*[!0-9.:]*) ;;
|
|
*)
|
|
if printf '%s\n' "$$v" | grep -Eqx "(10(\.$$o){3}|172\.(1[6-9]|2[0-9]|3[01])(\.$$o){2}|192\.168(\.$$o){2}):$$p"; then
|
|
echo "frontend-bind-check: frontend 를 $$v 에만 게시한다 (사설 IPv4)"
|
|
exit 0
|
|
fi
|
|
;;
|
|
esac
|
|
echo "frontend-bind-check: FRONTEND_PRIVATE_BIND=\"$$v\" 는 쓸 수 없어 frontend 를 띄우지 않습니다 — <이 서버의 사설 IPv4>:<포트> 만 받습니다 (10.x · 172.16~31.x · 192.168.x, 예 10.0.1.12:8080). 포트뿐인 값 · 0.0.0.0 · 공인 IP · IPv6 는 받지 않습니다 (mail/README.md \"리버스 프록시가 다른 서버에 있을 때\")" >&2
|
|
exit 1
|
|
|
|
networks:
|
|
# base 의 external proxy 네트워크를 지운다 — 이 서버에 없는 네트워크를 찾다 up 이 멈추지 않게
|
|
proxy: !reset null
|