설정이 .env(compose 주입)·config.toml·코드 곳곳의 os.environ 직독 3계층에 흩어져 관리가 어려웠다. TOML 하나로 통합한다(협의 결정). - 신설 [WorkerConfig](동시성·폴백·프로필·데드라인·유예·Chrome·하트비트), [AlertConfig](웹훅·쿨다운·임계 10종). [WebServerConfig].api_keys(guard), [DecodoConfig].ip_request_budget/port_cooldown_sec 추가 — 흩어져 있던 LPS_* env 20여 개를 섹션으로 흡수. - server_configs 의 env override 계층(DB_*·시크릿·NAVER_KEYS 등) 삭제. 남는 env 는 APP_ENV(부트스트랩)·PROCESS_COUNT/WORKER_CONCURRENCY(실행 스크립트 대화형 입력 전용)·LPS_LIVE(테스트 옵트인)뿐. - Docker: env 주입 → config.docker.toml 마운트 + APP_ENV=docker. 이미지 무시크릿 유지, 마운트 누락 시 FileNotFoundError 즉시 실패. .env.example 삭제, config.docker.toml.example 신설. - negodata 호출부: guard 키를 env 직독에서 [WebServerConfig].lps_api_key (+기존 관례대로 env override)로 이동. - 실행 스크립트: 프로필·폴백·예산 프롬프트 제거(toml 소스 안내), 동시성/프로세스 수만 임시 override 로 유지. - docs 7종·example toml 의 env 표기를 toml 키로 일괄 갱신. - 전체 145 passed + APP_ENV=docker 로딩·API 기동 스모크 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
188 lines
8.2 KiB
YAML
188 lines
8.2 KiB
YAML
# 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
|
|
# agent 서버: http://localhost:9500/docs
|
|
# anchoring 배치: 포트 없음 — 상주 스케줄러(격주 토 00:00 KST), docker logs anchoring 으로 확인
|
|
#
|
|
# DB 준비(최초 1회): postgres-init 의 SQL 을 대상 DB 에 적용한다.
|
|
# psql -h <host> -p <port> -U <user> -f postgres-init/00-init.sql (스키마 전체: negosium_db + 도메인·learning·anchoring schema)
|
|
# psql -h <host> -p <port> -U <user> -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
|
|
# LPS API guard 키는 negodata 의 config.local.toml [WebServerConfig].lps_api_key 로 관리(개발은 빈값=개방)
|
|
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
|
|
|
|
# 협상 에이전트 (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 로 주입.
|
|
# ── 설정은 TOML 하나(2026-07-13 협의 — .env/env 주입 제거) ──
|
|
# ./lps/config/config.docker.toml(미커밋, example 복사 후 값 채움)을 마운트하고 APP_ENV=docker 로 읽는다.
|
|
# 이미지에는 시크릿이 없고, 마운트를 잊으면 기동 시 FileNotFoundError 로 즉시 실패한다.
|
|
# 값 변경 = toml 수정 + docker compose restart (스케일·알림·guard 키 전부 toml 섹션에서).
|
|
lps-api:
|
|
build:
|
|
context: ./lps
|
|
dockerfile: Dockerfile
|
|
container_name: lps-api
|
|
environment:
|
|
APP_ENV: docker
|
|
PYTHONUNBUFFERED: "1"
|
|
volumes:
|
|
- ./lps/config/config.docker.toml:/app/config/config.docker.toml:ro
|
|
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: docker # → 마운트된 config.docker.toml 사용(동시성·폴백·예산·알림 전부 toml)
|
|
PYTHONUNBUFFERED: "1"
|
|
volumes:
|
|
- ./lps/config/config.docker.toml:/app/config/config.docker.toml:ro
|
|
- lps-profiles:/profiles # Chrome 프로필(쿠키) 영속 — [WorkerConfig].profile_dir=/profiles
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시
|
|
stop_grace_period: 75s # graceful 종료 유예([WorkerConfig].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:
|