# Negosium + Negodata 백엔드. # DB 는 compose 에서 관리하지 않는다 — 각 backend 는 config.docker.toml 의 접속 정보대로 # 외부 PostgreSQL 에 연결한다(호스트에 떠 있는 로컬 postgres, 또는 따로 실행 중인 docker postgres). # 컨테이너에서 호스트의 DB 에 접속할 때는 host.docker.internal 을 쓴다. # # docker compose up -d # negosium 서버: http://localhost:9300/docs # negosium 프론트: http://localhost:3300 # negodata 서버: http://localhost:9400/docs # 솔루션 랜딩: http://localhost:3100 # agent 서버: http://localhost:9500/docs # anchoring 배치: 포트 없음 — 상주 스케줄러(격주 토 00:00 KST), docker logs anchoring 으로 확인 # # DB 준비(최초 1회): postgres-init 의 SQL 을 대상 DB 에 적용한다. # psql -h -p -U -f postgres-init/00-init.sql (스키마 전체: negosium_db + 도메인·learning·anchoring schema) # psql -h -p -U -f postgres-init/temp-data.sql (임시 데이터 시드: admin / admin1234, company.users) services: negosium-backend: build: ./backend container_name: negosium-backend environment: APP_ENV: local DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override) AGENT_BASE_URL: http://host.docker.internal:9500 # 컨테이너→호스트 agent (로컬 uvicorn :9500) ports: - "9300:9300" # 컨테이너에서 호스트의 DB 로 접근 (config.docker.toml 의 host.docker.internal) extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped negodata-backend: build: ./negodata/backend container_name: negodata-backend environment: APP_ENV: local DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override) RELOAD: "1" # uvicorn --reload 활성 → 소스 저장 시 자동 재기동(재빌드 불필요) SCHEDULER_ENABLED: "1" # 마감 크론 활성(단일 워커라 중복 없음). 운영 다중 워커면 1개 프로세스에서만 1 PYTHONUNBUFFERED: "1" # 컨테이너 로그 실시간 출력(stdout 버퍼링 끔) # ── LPS(인터넷 최저가) 연동 — 미설정이면 연동 비활성으로 조용히 동작 ── LPS_DB_HOST: host.docker.internal # lps_db 읽기전용(수집 배치·조회 API) LPS_BASE_URL: http://host.docker.internal:9600 # 검색요청 enqueue. lps-api 컨테이너 사용 시 http://lps-api:9600 volumes: - ./negodata/backend:/app # 호스트 소스 = 컨테이너 코드. 이게 있어야 수정이 즉시 반영됨 ports: - "9400:9400" extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped # negodata 프론트 (Vite dev 서버). negodata-front: build: ./negodata/front container_name: negodata-front ports: - "3000:3000" volumes: - ./negodata/front:/app - /app/node_modules restart: unless-stopped # 솔루션 랜딩페이지 (react-router dev 서버. 배포는 `npm run build` 산출물 build/client 정적 서빙). landing: build: ./landing container_name: negosium-landing ports: - "3100:3100" volumes: - ./landing:/app - /app/node_modules restart: unless-stopped # 협상 에이전트 (negosium_db 공유, learning 스키마 사용). agent: build: ./agent container_name: negosium-agent environment: APP_ENV: local DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override) OPENAI_API_KEY: ${OPENAI_API_KEY:-} # LLM 키 passthrough (호스트 env/.env → 컨테이너). 빈 값이면 toml 폴백 ports: - "9500:9500" extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped # negosium 공급사 프론트 (프로덕션 빌드 정적 서빙, :3300). 브라우저가 backend(:9300)를 직접 호출. negosium-front: build: ./frontend container_name: negosium-front ports: - "3300:3300" restart: unless-stopped # 앵커링 값 자동 조정 배치 (negosium_db 공유, 포트 없음 — 상주 스케줄러). anchoring: build: ./schedules/anchoring container_name: anchoring environment: APP_ENV: local # config.{APP_ENV}.toml 선택 (local/dev/prod) DB_HOST: host.docker.internal # 컨테이너→호스트 DB REDIS_HOST: anchoring-redis TZ: Asia/Seoul volumes: - ./schedules/anchoring/config.local.toml:/app/config.local.toml:ro # 시크릿은 마운트 — up 전에 파일 필요 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(요청 접수, lean) + 워커(크롤, 헤드풀 Chromium+Xvfb). DB 는 외부(host.docker.internal). # 이미지엔 시크릿이 없다(example config 로 빌드) — 실값은 아래 env 로 주입. # 시크릿 값은 리포 루트 .env 파일에 채운다(.env.example 참고, .env 는 미커밋). lps-api: build: context: ./lps dockerfile: Dockerfile container_name: lps-api environment: APP_ENV: local DB_HOST: host.docker.internal # 컨테이너→호스트 DB (example toml 의 127.0.0.1 override) DB_USER: ${LPS_DB_USER:-postgres} DB_PASSWORD: ${LPS_DB_PASSWORD:-postgres} PYTHONUNBUFFERED: "1" # 멀티코어: PROCESS_COUNT(uvicorn 워커=코어수)를 올리면 커넥션 풀은 자동 산정된다. # (pool+overflow)×2엔진×PROCESS_COUNT ≤ DB_CONNECTION_BUDGET 를 config 가 스스로 보장. # 값은 .env 에서 서버별로 조정(compose 수정 불필요). API 병목은 드묾 — 기본 1이면 충분, # 부하테스트/대량 폴링 대비 시에만 코어 수만큼 상향(예: 4). PROCESS_COUNT: ${LPS_API_PROCESS_COUNT:-1} DB_CONNECTION_BUDGET: ${LPS_DB_CONNECTION_BUDGET:-40} # 전용 PG(max_connections≈100)면 90 근처로 상향 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 DB_HOST: host.docker.internal DB_USER: ${LPS_DB_USER:-postgres} DB_PASSWORD: ${LPS_DB_PASSWORD:-postgres} PYTHONUNBUFFERED: "1" WORKER_CONCURRENCY: "1" # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 4×N) LPS_PROFILE_DIR: /profiles # Chrome 프로필을 영속 볼륨에 → 재시작해도 cf_clearance 유지(재웜업 회피) # LPS_FALLBACKS: "gmarket,auction,st11" # 오픈마켓 폴백(기본 OFF — 켜기 전 라이브 스모크로 셀렉터 점검) # LPS_JOB_DEADLINE_SEC: "300" # 잡 1건 처리 상한(행 방어) — 기본 300s # ── 시크릿 주입(이미지엔 없음 — 필수). 리포 루트 .env 에 값 채움(.env.example 참고) ── OPENAI_API_KEY: ${OPENAI_API_KEY:-} # 비면 AI 판정 OFF NAVER_KEYS: ${NAVER_KEYS:-} # "id1:secret1,id2:secret2" — 비면 네이버 검색 실패 DECODO_HOST: ${DECODO_HOST:-} # DECODO 4종 비면 프록시 미사용(직접 연결) DECODO_USERNAME: ${DECODO_USERNAME:-} DECODO_PASSWORD: ${DECODO_PASSWORD:-} DECODO_PORT_START: ${DECODO_PORT_START:-0} DECODO_PORT_END: ${DECODO_PORT_END:-0} DECODO_COST_PER_GB: ${DECODO_COST_PER_GB:-0} volumes: - lps-profiles:/profiles # Chrome 프로필(쿠키) 영속 extra_hosts: - "host.docker.internal:host-gateway" shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시 stop_grace_period: 75s # graceful 종료 유예(LPS_SHUTDOWN_GRACE_SEC=60 + 정리 여유) — 기본 10s 면 하던 잡 마무리 전에 SIGKILL labels: autoheal: "true" # 하트비트 HEALTHCHECK 실패(행/좀비) 시 autoheal 이 재시작 restart: unless-stopped logging: driver: json-file options: { max-size: "10m", max-file: "5" } # HEALTHCHECK 실패 컨테이너 자동 재시작 — compose 의 restart 는 '프로세스 종료'만 다루고 # unhealthy 는 표시만 하므로, 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: