배치 실계좌에서 종목조회로 변경
This commit is contained in:
18
backend/app/core/logging_config.py
Normal file
18
backend/app/core/logging_config.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""공통 로깅 설정 — KST(Asia/Seoul) 타임존 포매터 제공."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
KST = pytz.timezone("Asia/Seoul")
|
||||||
|
|
||||||
|
|
||||||
|
class KSTFormatter(logging.Formatter):
|
||||||
|
"""%(asctime)s 를 KST(UTC+9) 기준으로 출력하는 포매터."""
|
||||||
|
|
||||||
|
def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
|
||||||
|
ct = datetime.datetime.fromtimestamp(record.created, tz=KST)
|
||||||
|
if datefmt:
|
||||||
|
return ct.strftime(datefmt)
|
||||||
|
t = ct.strftime(self.default_time_format)
|
||||||
|
return self.default_msec_format % (t, record.msecs)
|
||||||
@@ -27,22 +27,24 @@ from app.core.market_schedule import is_market_open
|
|||||||
# 로깅 설정
|
# 로깅 설정
|
||||||
import os
|
import os
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
from app.core.logging_config import KSTFormatter
|
||||||
_log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)
|
_log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)
|
||||||
_log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
_log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
_log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "logs")
|
_log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "logs")
|
||||||
os.makedirs(_log_dir, exist_ok=True)
|
os.makedirs(_log_dir, exist_ok=True)
|
||||||
_log_file = os.path.join(_log_dir, "backend.log")
|
_log_file = os.path.join(_log_dir, "backend.log")
|
||||||
|
|
||||||
logging.basicConfig(
|
_formatter = KSTFormatter(_log_format)
|
||||||
level=_log_level,
|
_handlers: list[logging.Handler] = [
|
||||||
format=_log_format,
|
|
||||||
handlers=[
|
|
||||||
logging.StreamHandler(),
|
logging.StreamHandler(),
|
||||||
logging.handlers.RotatingFileHandler(
|
logging.handlers.RotatingFileHandler(
|
||||||
_log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
_log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
for _h in _handlers:
|
||||||
|
_h.setFormatter(_formatter)
|
||||||
|
|
||||||
|
logging.basicConfig(level=_log_level, handlers=_handlers)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,40 @@ class KoreaInvestmentAPI:
|
|||||||
# 토큰 캐시 파일 경로 — 사용자 홈 디렉토리 하위에 저장 (/tmp는 보안 취약)
|
# 토큰 캐시 파일 경로 — 사용자 홈 디렉토리 하위에 저장 (/tmp는 보안 취약)
|
||||||
TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache.json"
|
TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache.json"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_real_market_data(cls) -> "KoreaInvestmentAPI":
|
||||||
|
"""실계좌 자격증명으로 시세조회 전용 인스턴스 생성.
|
||||||
|
|
||||||
|
모의투자 모드에서도 실계좌 API로 시세를 조회할 때 사용한다.
|
||||||
|
(모의투자 서버는 KOSDAQ 거래량 순위 등 일부 조회 API를 지원하지 않음)
|
||||||
|
주문 API는 절대 호출하지 말 것.
|
||||||
|
"""
|
||||||
|
if not settings.KIS_REAL_APP_KEY:
|
||||||
|
raise ValueError("KIS_REAL_APP_KEY가 설정되지 않아 실계좌 시세조회를 사용할 수 없습니다.")
|
||||||
|
instance = cls.__new__(cls)
|
||||||
|
instance.base_url = settings.KIS_REAL_URL
|
||||||
|
instance.app_key = settings.KIS_REAL_APP_KEY
|
||||||
|
instance.app_secret = settings.KIS_REAL_APP_SECRET
|
||||||
|
instance.account_number = settings.KIS_REAL_ACCOUNT_NO
|
||||||
|
instance.access_token = None
|
||||||
|
instance.token_expires_at = None
|
||||||
|
instance.last_request_time = None
|
||||||
|
instance.TOKEN_CACHE_FILE = Path.home() / ".cache" / "auto-stock" / "kis_token_cache_real.json"
|
||||||
|
instance.min_request_interval = settings.API_MIN_REQUEST_INTERVAL
|
||||||
|
instance.max_retries = settings.API_MAX_RETRIES
|
||||||
|
instance.retry_delay = settings.API_RETRY_DELAY
|
||||||
|
instance.retry_backoff = settings.API_RETRY_BACKOFF
|
||||||
|
instance.timeout = settings.API_TIMEOUT
|
||||||
|
instance.semaphore = asyncio.Semaphore(settings.API_MAX_CONCURRENT_REQUESTS)
|
||||||
|
instance._token_lock = asyncio.Lock()
|
||||||
|
instance.monitor = APICallMonitor(window_size=settings.API_MONITORING_WINDOW)
|
||||||
|
logger.info(
|
||||||
|
"🔧 KIS 실계좌 시세조회 API 초기화: base_url=%s, app_key=%s...",
|
||||||
|
instance.base_url, instance.app_key[:6]
|
||||||
|
)
|
||||||
|
instance._load_token_from_cache()
|
||||||
|
return instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.base_url = settings.KOREA_INVESTMENT_API_BASE_URL
|
self.base_url = settings.KOREA_INVESTMENT_API_BASE_URL
|
||||||
self.app_key = settings.KOREA_INVESTMENT_APP_KEY
|
self.app_key = settings.KOREA_INVESTMENT_APP_KEY
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Cron: 0 9 * * 1-5 cd /path/to/auto-stock/backend && python batch_daily_analysis.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ import logging
|
|||||||
# 인프로세스 스케줄러(main.py)로 실행될 때 로그 설정 충돌 방지
|
# 인프로세스 스케줄러(main.py)로 실행될 때 로그 설정 충돌 방지
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 성능 설정
|
# 성능 설정
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
@@ -59,29 +60,28 @@ async def detect_and_log_market_regime() -> str:
|
|||||||
|
|
||||||
BULL / BEAR / SIDEWAYS 중 하나를 리턴하며 배치 전체에 로깅된다.
|
BULL / BEAR / SIDEWAYS 중 하나를 리턴하며 배치 전체에 로깅된다.
|
||||||
실패 시 UNKNOWN을 리턴하여 배치를 차단하지 않는다.
|
실패 시 UNKNOWN을 리턴하여 배치를 차단하지 않는다.
|
||||||
|
|
||||||
|
pykrx 대신 KIS API 사용 — KODEX 200(069500)으로 KOSPI 레짐 판별.
|
||||||
|
KODEX 200은 KOSPI 200을 추종하므로 지수 대용으로 적합하다.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from pykrx import stock as pykrx_stock
|
|
||||||
from app.services.mean_reversion_strategy import detect_market_regime
|
from app.services.mean_reversion_strategy import detect_market_regime
|
||||||
|
|
||||||
today = datetime.now(KST).strftime("%Y%m%d")
|
# KODEX 200(069500) 일봉 240개 = 약 1년치 데이터
|
||||||
start = (datetime.now(KST) - timedelta(days=400)).strftime("%Y%m%d")
|
data = await korea_investment_api.get_daily_chart("069500", period="D", limit=240)
|
||||||
|
output = data.get("output2") or []
|
||||||
|
|
||||||
# KOSPI 지수 코드 1001
|
if not isinstance(output, list) or len(output) < 205:
|
||||||
df = pykrx_stock.get_index_ohlcv_by_date(start, today, "1001")
|
logger.warning("⚠️ KODEX200 데이터 부족(%d행) — 레짐 판별 스킵", len(output) if output else 0)
|
||||||
|
|
||||||
# 🔧 [fix] df=None일 때 len(df) 직접 호출 방지
|
|
||||||
df_len = len(df) if df is not None else 0
|
|
||||||
if df_len < 205:
|
|
||||||
logger.warning("⚠️ KOSPI 데이터 부족(행 %d) — 레짐 판별 스킵", df_len)
|
|
||||||
return "UNKNOWN"
|
return "UNKNOWN"
|
||||||
|
|
||||||
kospi_closes = df["종가"].tolist()
|
# KIS API는 최신순 반환 → 오래된 순(시계열 순)으로 역정렬
|
||||||
regime = detect_market_regime(kospi_closes)
|
closes = [float(item["stck_clpr"]) for item in reversed(output) if item.get("stck_clpr")]
|
||||||
|
regime = detect_market_regime(closes)
|
||||||
|
|
||||||
regime_emoji = {"상승장": "📈 BULL", "하락장": "📉 BEAR",
|
regime_emoji = {"상승장": "📈 BULL", "하락장": "📉 BEAR",
|
||||||
"bull": "📈 BULL", "bear": "📉 BEAR"}.get(regime.value, "➡️ " + regime.value.upper())
|
"bull": "📈 BULL", "bear": "📉 BEAR"}.get(regime.value, "➡️ " + regime.value.upper())
|
||||||
logger.info("🌏 시장 레짐: %s (KOSPI %s일 기준)", regime_emoji, today)
|
logger.info("🌏 시장 레짐: %s (KODEX200 %d일 기준)", regime_emoji, len(closes))
|
||||||
return regime.value.upper()
|
return regime.value.upper()
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -94,65 +94,88 @@ async def detect_and_log_market_regime() -> str:
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
async def get_all_tradable_stocks(kis_api: KoreaInvestmentAPI, limit: int = 500) -> list[tuple[str, str]]:
|
async def get_all_tradable_stocks(kis_api: KoreaInvestmentAPI, limit: int = 500) -> list[tuple[str, str]]:
|
||||||
"""시가총액 상위 종목 조회 (pykrx 사용)
|
"""거래량 상위 종목 조회 (KIS API 사용)
|
||||||
|
|
||||||
ETF / 레버리지 / 스팭 등 특수종목 자동 제외.
|
pykrx 의존성 제거 — KIS 거래량 순위 API(FHPST01710000) 사용.
|
||||||
KOSPI + KOSDAQ 합산 시가총액 상위 limit개 반환.
|
시가총액 기준 → 거래량 기준으로 변경 (유동성 높은 종목 우선).
|
||||||
|
ETF / 레버리지 / 스팩 등 특수종목 자동 제외.
|
||||||
|
KOSPI + KOSDAQ 합산 거래량 상위 limit개 반환.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
RuntimeError: 조회 결과가 MIN_STOCK_COUNT(50) 미만인 경우 배치 중단
|
RuntimeError: 조회 결과가 MIN_STOCK_COUNT(50) 미만인 경우 배치 중단
|
||||||
"""
|
"""
|
||||||
logger.info("Fetching top %d stocks by market cap from pykrx...", limit)
|
logger.info("Fetching top %d stocks by volume from KIS API...", limit)
|
||||||
try:
|
|
||||||
from pykrx import stock as pykrx_stock
|
|
||||||
|
|
||||||
today = datetime.now(KST).strftime("%Y%m%d")
|
|
||||||
|
|
||||||
SKIP_KEYWORDS = [
|
SKIP_KEYWORDS = [
|
||||||
"ETF", "레버리지", "인버스", "KODEX", "TIGER", "ARIRANG",
|
"ETF", "레버리지", "인버스", "KODEX", "TIGER", "ARIRANG",
|
||||||
"KBSTAR", "HANARO", "SOL", "ACE", "KOSEF", "KINDEX",
|
"KBSTAR", "HANARO", "SOL", "ACE", "KOSEF", "KINDEX",
|
||||||
"선물", "스팭", "리츠", "SPAC",
|
"선물", "스팩", "리츠", "SPAC",
|
||||||
]
|
]
|
||||||
|
|
||||||
stocks_with_cap = []
|
# 모의투자 모드에서 실계좌 시세 API를 사용할 수 있으면 준비
|
||||||
for market in ["KOSPI", "KOSDAQ"]:
|
real_market_api: Optional[KoreaInvestmentAPI] = None
|
||||||
|
if settings.KIS_MODE == "virtual" and settings.KIS_REAL_APP_KEY:
|
||||||
try:
|
try:
|
||||||
cap_df = pykrx_stock.get_market_cap_by_ticker(today, market=market)
|
real_market_api = KoreaInvestmentAPI.for_real_market_data()
|
||||||
logger.info(" %s: %d개 종목 시가총액 조회", market, len(cap_df))
|
logger.info("📡 KOSDAQ 조회: 실계좌 시세 API 사용 (모의투자 서버 제한 우회)")
|
||||||
for ticker in cap_df.index:
|
except Exception as exc:
|
||||||
|
logger.warning("⚠️ 실계좌 시세 API 초기화 실패, 모의투자 API 사용: %s", exc)
|
||||||
|
|
||||||
|
stocks_with_vol = []
|
||||||
|
for market_code, market_name in [("J", "KOSPI"), ("Q", "KOSDAQ")]:
|
||||||
|
# KOSDAQ은 실계좌 시세 API 우선 사용 (모의투자 서버는 KOSDAQ 미지원)
|
||||||
|
api_to_use = real_market_api if (market_code == "Q" and real_market_api) else kis_api
|
||||||
try:
|
try:
|
||||||
name = pykrx_stock.get_market_ticker_name(ticker)
|
result = await api_to_use.get_volume_rank(market_code)
|
||||||
|
items = result.get("output") or []
|
||||||
|
|
||||||
|
if not isinstance(items, list):
|
||||||
|
logger.warning(" %s: output이 리스트 아님 — 건너뜀", market_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for item in items:
|
||||||
|
ticker = item.get("mksc_shrn_iscd") or item.get("stck_shrn_iscd") or ""
|
||||||
|
name = item.get("hts_kor_isnm") or item.get("stck_kor_isnm") or ""
|
||||||
|
volume = int(item.get("acml_vol") or 0)
|
||||||
|
|
||||||
|
if not ticker or not name:
|
||||||
|
continue
|
||||||
if any(k in name for k in SKIP_KEYWORDS):
|
if any(k in name for k in SKIP_KEYWORDS):
|
||||||
continue
|
continue
|
||||||
stocks_with_cap.append((ticker, name, cap_df.loc[ticker, "시가총액"], market))
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error("❌ %s 종목 조회 실패: %s", market, exc)
|
|
||||||
|
|
||||||
stocks_with_cap.sort(key=lambda x: x[2], reverse=True)
|
stocks_with_vol.append((ticker, name, volume, market_name))
|
||||||
top_with_mkt = stocks_with_cap[:limit]
|
count += 1
|
||||||
|
|
||||||
|
logger.info(" %s: %d개 종목 조회", market_name, count)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("❌ %s 종목 조회 실패: %s", market_name, exc)
|
||||||
|
|
||||||
|
stocks_with_vol.sort(key=lambda x: x[2], reverse=True)
|
||||||
|
top_with_mkt = stocks_with_vol[:limit]
|
||||||
top_stocks = [(code, name) for code, name, _, _mkt in top_with_mkt]
|
top_stocks = [(code, name) for code, name, _, _mkt in top_with_mkt]
|
||||||
|
|
||||||
kospi_n = sum(1 for *_, mkt in top_with_mkt if mkt == "KOSPI")
|
kospi_n = sum(1 for *_, mkt in top_with_mkt if mkt == "KOSPI")
|
||||||
kosdaq_n = sum(1 for *_, mkt in top_with_mkt if mkt == "KOSDAQ")
|
kosdaq_n = sum(1 for *_, mkt in top_with_mkt if mkt == "KOSDAQ")
|
||||||
logger.info("✅ 선정 %d개 (KOSPI %d / KOSDAQ %d)", len(top_stocks), kospi_n, kosdaq_n)
|
logger.info("✅ 선정 %d개 (KOSPI %d / KOSDAQ %d)", len(top_stocks), kospi_n, kosdaq_n)
|
||||||
|
|
||||||
# 🔧 [fix] 최소 종목 수 검증 — 너무 적으면 잘못된 데이터로 배치 실행 방지
|
|
||||||
if len(top_stocks) < MIN_STOCK_COUNT:
|
if len(top_stocks) < MIN_STOCK_COUNT:
|
||||||
|
if settings.KIS_MODE == "virtual":
|
||||||
|
# 모의투자 서버는 KOSDAQ 거래량 순위 미지원 — 조회된 종목으로 진행
|
||||||
|
logger.warning(
|
||||||
|
"⚠️ 모의투자 서버 종목 수 제한: %d개 (기준 %d개). "
|
||||||
|
"KOSDAQ 거래량 순위 미지원으로 인한 정상 동작 — 배치 계속 진행합니다.",
|
||||||
|
len(top_stocks), MIN_STOCK_COUNT
|
||||||
|
)
|
||||||
|
else:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"종목 조회 결과 너무 적음: {len(top_stocks)}개 (최소 {MIN_STOCK_COUNT}개 필요). "
|
f"종목 조회 결과 너무 적음: {len(top_stocks)}개 (최소 {MIN_STOCK_COUNT}개 필요). "
|
||||||
"pykrx API 오류 또는 장 휴장 여부를 확인하세요."
|
"KIS API 오류 또는 장 휴장 여부를 확인하세요."
|
||||||
)
|
)
|
||||||
|
|
||||||
return top_stocks
|
return top_stocks
|
||||||
|
|
||||||
except RuntimeError:
|
|
||||||
raise # 최소 종목 수 미달 — 상위로 전파하여 배치 중단
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error("❌ 종목 목록 조회 실패: %s", exc, exc_info=True)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# 3. 단일 종목 분석 (순수 분석만 — DB 저장 없음)
|
# 3. 단일 종목 분석 (순수 분석만 — DB 저장 없음)
|
||||||
@@ -392,8 +415,9 @@ async def main():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 🔧 [fix] basicConfig를 __main__ 블록으로 이동
|
# 🔧 [fix] basicConfig를 __main__ 블록으로 이동
|
||||||
# 인프로세스 스케줄러(main.py lifespan)에서 import될 때 로그 설정 충돌 방지
|
# 인프로세스 스케줄러(main.py lifespan)에서 import될 때 로그 설정 충돌 방지
|
||||||
logging.basicConfig(
|
from app.core.logging_config import KSTFormatter
|
||||||
level=logging.INFO,
|
_fmt = KSTFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
_handler = logging.StreamHandler()
|
||||||
)
|
_handler.setFormatter(_fmt)
|
||||||
|
logging.basicConfig(level=logging.INFO, handlers=[_handler])
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ services:
|
|||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: autostock-postgres
|
container_name: autostock-postgres
|
||||||
environment:
|
environment:
|
||||||
|
TZ: Asia/Seoul
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
@@ -24,6 +25,8 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: autostock-redis
|
container_name: autostock-redis
|
||||||
|
environment:
|
||||||
|
TZ: Asia/Seoul
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -41,6 +44,7 @@ services:
|
|||||||
image: rabbitmq:3-management
|
image: rabbitmq:3-management
|
||||||
container_name: autostock-rabbitmq
|
container_name: autostock-rabbitmq
|
||||||
environment:
|
environment:
|
||||||
|
TZ: Asia/Seoul
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||||
ports:
|
ports:
|
||||||
@@ -64,6 +68,7 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: autostock-mcp-server
|
container_name: autostock-mcp-server
|
||||||
environment:
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
- MCP_SERVER_HOST=${MCP_SERVER_HOST}
|
- MCP_SERVER_HOST=${MCP_SERVER_HOST}
|
||||||
- MCP_SERVER_PORT=${MCP_SERVER_PORT}
|
- MCP_SERVER_PORT=${MCP_SERVER_PORT}
|
||||||
@@ -88,6 +93,7 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: autostock-backend
|
container_name: autostock-backend
|
||||||
environment:
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
- DATABASE_URL=${DATABASE_URL}
|
- DATABASE_URL=${DATABASE_URL}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
- RABBITMQ_URL=${RABBITMQ_URL}
|
- RABBITMQ_URL=${RABBITMQ_URL}
|
||||||
@@ -131,6 +137,7 @@ services:
|
|||||||
image: prodrigestivill/postgres-backup-local:15
|
image: prodrigestivill/postgres-backup-local:15
|
||||||
container_name: autostock-postgres-backup
|
container_name: autostock-postgres-backup
|
||||||
environment:
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
- POSTGRES_HOST=postgres
|
- POSTGRES_HOST=postgres
|
||||||
- POSTGRES_DB=${POSTGRES_DB}
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
- POSTGRES_USER=${POSTGRES_USER}
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
@@ -158,6 +165,8 @@ services:
|
|||||||
- VITE_API_URL=${VITE_API_URL}
|
- VITE_API_URL=${VITE_API_URL}
|
||||||
- VITE_WS_URL=${VITE_WS_URL}
|
- VITE_WS_URL=${VITE_WS_URL}
|
||||||
container_name: autostock-frontend
|
container_name: autostock-frontend
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
ports:
|
ports:
|
||||||
- "3000:80"
|
- "3000:80"
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
Reference in New Issue
Block a user