Files
arasystem/docker-compose.remote-proxy.yml
ttipo 73babeaa2c feat(docker): 13단계 운영 기반 — 백업 · 감시 · 관리자 단단히 · 휴지통 30일 · 메일 프로그램 자동 설정
전 직원 전환 전에 서버가 스스로 지키고 되살릴 수 있게 했다.
- 관리자 단단히(setup): 관리자 역할에서 impersonate 를 빼고 관리자 허용 IP 를 앱 네트워크로 — 관리자 비밀번호로 바깥 993 · JMAP 에서 직원 메일함에 대리 로그인되던 길을 막았다.
  운영에서 처음 걸 때는 되돌릴 복구 관리자가 있을 때만 건다(관리자 잠김 방지).
- 휴지통 · 스팸 30일 정리를 KST 03:00 으로 명시. imapsync 로 옮긴 메일도 옮긴 때부터 센다(임시 엔진 실측).
- restic 백업(호스트 systemd 03:30 — 메일 볼륨 warm → 엔진 멈춤 9초 안팎 → final, pg_dump, 업로드, env) · 복원(새 서버 차례를 빈 클론에서 실측) · 15분 점검 · 부팅 되살리기, Healthchecks 하트비트.
- 메일 프로그램 자동 설정: frontend nginx 의 autoconfig · autodiscover 고정 응답(엔진 것은 POP3 를 알리고 입력을 되돌려 쓴다).
- 모든 서비스 docker 로그 보존, DB 이미지 판 · digest 고정.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:44:44 +09:00

83 lines
5.1 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"
# 로그 보존 — docker-compose.yml 의 logging-default 와 같은 값 (한 번 돌고 끝나는 컨테이너라 몇 줄뿐이다)
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
compress: "true"
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