services: # ── 프론트 (React/Vite → 정적 빌드 → nginx) ────────────────────────────── negodata-front: build: context: ./negodata/front dockerfile: Dockerfile.prod container_name: negodata-front ports: - "${NEGODATA_FRONT_PORT:-30012}:80" depends_on: - negodata-backend # nginx 가 시작 시 upstream(negodata-backend) 이름을 해석해야 함 restart: unless-stopped negosium-front: build: context: ./frontend dockerfile: Dockerfile.prod container_name: negosium-front ports: - "${NEGOSIUM_FRONT_PORT:-30010}:80" depends_on: - negosium-backend restart: unless-stopped # ── 솔루션 랜딩 (React Router SSG → 정적 빌드 → nginx) ─────────────────── landing: build: context: ./landing dockerfile: Dockerfile.prod container_name: negosium-landing ports: - "${LANDING_PORT:-30013}:80" restart: unless-stopped # ── 백엔드 (FastAPI, 기존 Dockerfile) ──────────────────────────────────── negodata-backend: build: ./negodata/backend container_name: negodata-backend environment: APP_ENV: prod SCHEDULER_ENABLED: "1" # 견적 자동마감 크론(process_count=1 단일 워커라 중복 없음) PYTHONUNBUFFERED: "1" LPS_BASE_URL: http://lps-api:9600 # 최저가 검색요청 enqueue — 같은 compose 망의 lps-api 컨테이너(미설정 시 localhost:9600 → 연결실패) # LPS 읽기 DB(lps_db)는 config.prod.toml 의 [LpsDBConfig](172.30.1.36/o2o_db_admin) 이 담당 → 별도 env 불필요 DB_HOST: ${DB_HOST} # toml 의 127.0.0.1 을 외부 DB 로 override DB_PORT: ${DB_PORT:-5432} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME:-negosium_db} volumes: - ./negodata/backend/config/config.prod.toml:/app/config/config.prod.toml:ro # 외부 비노출 — negodata-front 의 nginx 가 /v1 을 내부망으로 프록시. extra_hosts: - "host.docker.internal:host-gateway" # DB 가 이 서버 호스트면 DB_HOST=host.docker.internal restart: unless-stopped negosium-backend: build: ./backend container_name: negosium-backend environment: APP_ENV: prod AGENT_BASE_URL: http://agent:9500 # 내부망으로 agent 호출 PYTHONUNBUFFERED: "1" DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT:-5432} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME:-negosium_db} volumes: - ./backend/config/config.prod.toml:/app/config/config.prod.toml:ro # 외부 비노출 — negosium-front 의 nginx 가 /v1 을 내부망으로 프록시. extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped # ── 협상 에이전트 (Python, 내부 전용) ──────────────────────────────────── agent: build: ./agent container_name: negosium-agent environment: APP_ENV: prod PYTHONUNBUFFERED: "1" DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT:-5432} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME:-negosium_db} volumes: - ./agent/config/config.prod.toml:/app/config/config.prod.toml:ro extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped # ── 앵커링 값 자동 조정 배치 (negosium_db 공유, 포트 없음 — 상주 스케줄러) ── # 이미지는 config.{APP_ENV}.toml 을 읽는다(dev/prod 는 파일 없으면 기동 중단). DB/시크릿은 # config.prod.toml(172.30.1.36) 에서 온다 — env(DB_*/REDIS_*) 를 설정하면 파일값을 override. anchoring: build: ./schedules/anchoring container_name: anchoring environment: APP_ENV: prod # → config.prod.toml 선택 (local 로 두면 빈 config.local 로 기동 실패) REDIS_HOST: anchoring-redis # 전용 캐시 컨테이너 사용(config 의 redis host 를 override) TZ: Asia/Seoul volumes: - ./schedules/anchoring/config.prod.toml:/app/config.prod.toml:ro # DB 접속·시크릿은 파일에서(마운트 필수) depends_on: - anchoring-redis extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped logging: # 상주 배치 — 장기 운영 디스크 보호 driver: json-file options: max-size: "10m" max-file: "5" # 앵커링 조회 캐시 (anchoring 전용) anchoring-redis: image: redis:7-alpine container_name: anchoring-redis ports: - "127.0.0.1:6380:6379" # 호스트 로컬만 개방 (무인증 Redis). 6380 = 호스트 redis(6379)와 충돌 회피 restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "5" # ── LPS (인터넷 최저가 검색) ────────────────────────────────── # API(요청 접수) + 워커(크롤, Chromium+Xvfb). # 이미지는 APP_ENV=local 고정 → config.local.toml 을 읽는다. prod 실값(DB 172.30.1.36 / # o2o_db_admin / negosium_db + OpenAI·Naver·Decodo 시크릿)은 config.prod.toml 을 그 경로에 # 마운트해 주입한다(다른 prod 서비스와 동일한 파일 기반 방식). DB_* env 는 주지 않는다 — # 주면 파일값을 빈 값/오타로 덮어써 인증 실패(예: 사용자 o2oadmin)한다. lps-api: build: context: ./lps dockerfile: Dockerfile container_name: lps-api environment: APP_ENV: local # 이미지 고정값 — 변경 금지(config.prod.toml 을 직접 읽지 않음) PYTHONUNBUFFERED: "1" PROCESS_COUNT: ${LPS_API_PROCESS_COUNT:-1} DB_CONNECTION_BUDGET: ${LPS_DB_CONNECTION_BUDGET:-40} # 전용 PG(max_connections≈100)면 90 근처로 상향 volumes: - ./lps/config/config.prod.toml:/app/config/config.local.toml:ro # prod DB·시크릿 주입(APP_ENV=local 이 읽는 경로 덮어씀) ports: - "9600:9600" extra_hosts: - "host.docker.internal:host-gateway" labels: autoheal: "true" # HEALTHCHECK 실패 시 autoheal 이 재시작 restart: unless-stopped logging: driver: json-file options: { max-size: "10m", max-file: "5" } lps-worker: build: context: ./lps dockerfile: Dockerfile.worker # Chromium + Xvfb (headless 는 안티봇에 탐지됨) container_name: lps-worker environment: APP_ENV: local # 이미지 고정값 — 변경 금지 PYTHONUNBUFFERED: "1" WORKER_CONCURRENCY: "1" # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 4×N) LPS_PROFILE_DIR: /profiles # Chrome 프로필을 영속 볼륨에 → 재시작해도 cf_clearance 유지 # DB·시크릿(OpenAI/Naver/Decodo)은 아래 config.prod.toml 마운트에서 온다. env override 는 주지 않는다. volumes: - ./lps/config/config.prod.toml:/app/config/config.local.toml:ro # prod DB·시크릿 주입(APP_ENV=local 이 읽는 경로 덮어씀) - lps-profiles:/profiles # Chrome 프로필(쿠키) 영속 extra_hosts: - "host.docker.internal:host-gateway" shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시 stop_grace_period: 75s # graceful 종료 유예 — 기본 10s 면 하던 잡 마무리 전에 SIGKILL labels: autoheal: "true" # 하트비트 HEALTHCHECK 실패(행/좀비) 시 autoheal 이 재시작 restart: unless-stopped logging: driver: json-file options: { max-size: "10m", max-file: "5" } # LPS 관리자 UI (정적 React → nginx). /v1·/healthz·/readyz 는 nginx 가 lps-api:9600 으로 프록시(동일 compose 망). lps-admin: build: context: ./lps-admin dockerfile: Dockerfile container_name: lps-admin ports: - "${LPS_ADMIN_PORT:-30014}:80" depends_on: - lps-api restart: unless-stopped logging: driver: json-file options: { max-size: "10m", max-file: "5" } # HEALTHCHECK 실패 컨테이너 자동 재시작 — autoheal 라벨 붙은 컨테이너(lps-api/lps-worker)를 감시해 재시작. # docker.sock 마운트 = 도커 제어 권한이므로 신뢰 환경에서만 사용. autoheal: image: willfarrell/autoheal:latest container_name: autoheal environment: AUTOHEAL_CONTAINER_LABEL: autoheal volumes: - /var/run/docker.sock:/var/run/docker.sock restart: unless-stopped logging: driver: json-file options: { max-size: "10m", max-file: "5" } volumes: lps-profiles: