Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .env.local

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ __pycache__/
eval_results/
eval_data/
*.egg-info/
# Local environment files
.env
.env.local
.env.*
!.env.example
node_modules
.kiro
.vscode
Expand Down
36 changes: 36 additions & 0 deletions devops/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ============================================
# TradingAgents Web — Docker deployment env template
# Copy to .env (next to docker-compose.yml) and fill in real values.
# cp .env.docker.example .env
# NEVER commit a real .env — it is gitignored.
# ============================================

# ---------- AI provider (required) ----------
# OneInfinity AI (OpenAI-compatible). Defaults match tradingagents/default_config.py.
OPENAI_API_KEY=sk-replace-with-real-key
OPENAI_BASE_URL=https://api.oneinfinityai.com/v1
DEEP_THINK_LLM=gpt-5.5
QUICK_THINK_LLM=gpt-5.5
LLM_PROVIDER=openai
EMBEDDING_LLM=text-embedding-3-small
# EMBEDDING_API_KEY defaults to OPENAI_API_KEY if unset
# EMBEDDING_BASE_URL defaults to OPENAI_BASE_URL if unset

# ---------- Database (optional) ----------
# Default: SQLite file at /app/db/tradingagents.db (inside the backend container,
# persisted via the db_data volume). No DB service needed.
DATABASE_URL=sqlite+aiosqlite:///./db/tradingagents.db

# To use MySQL instead, uncomment the mysql service in docker-compose.yml and set:
# DATABASE_URL=mysql+aiomysql://tradingagents:tradingagents123@mysql:3306/tradingagents?charset=utf8mb4
# MYSQL_ROOT_PASSWORD=tradingagents123
# MYSQL_DATABASE=tradingagents
# MYSQL_USER=tradingagents
# MYSQL_PASSWORD=tradingagents123

# ---------- Data source API keys (optional — skills layer falls back gracefully) ----------
ALPHA_VANTAGE_API_KEY=
XUEQIU_TOKEN=

# ---------- Frontend (optional — composed sets this automatically) ----------
# FRONTEND_API_BASE_URL is set in docker-compose.yml; no need to set here.
93 changes: 93 additions & 0 deletions devops/Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ============================================
# TradingAgents Backend Dockerfile (FastAPI / uvicorn)
# Multi-stage, non-root, with healthcheck.
# Context: repo root (so tradingagents/, web/backend/, cli/, etc. are COPYable).
# ============================================

# ---------- Stage 1: builder ----------
FROM python:3.11-slim AS builder

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

# Build deps: gcc/g++ for C extensions, curl for healthcheck probe in runtime.
# Use debian mirrors (no aliyun override — keeps image portable across regions).
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
build-essential \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /build

# Install Python dependencies first (layer cache friendly).
# `ta-lib` is listed in requirements.txt but is OPTIONAL at runtime —
# akshare_indicator.py imports it inside try/except with a pandas fallback.
# The TA-Lib C library is painful to build in a slim image and brings no
# functional value over the pandas fallback for this app, so we filter it out.
COPY requirements.txt pyproject.toml setup.py ./
RUN grep -v -E '^(ta-lib|\s*ta-lib)\b' requirements.txt > /tmp/requirements.filtered.txt \
&& pip install --upgrade pip \
&& pip install -r /tmp/requirements.filtered.txt \
&& pip install "uvicorn[standard]" websockets

# ---------- Stage 2: runtime ----------
FROM python:3.11-slim AS runtime

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1 \
NODE_ENV=production \
TZ=Asia/Shanghai

# Runtime system deps: curl for healthcheck, tzdata for Asia/Shanghai,
# libstdc++6 / libgomp1 for numpy/pandas/etc.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
tzdata \
libstdc++6 \
libgomp1 \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& rm -rf /var/lib/apt/lists/*

# Non-root user
RUN groupadd --system app && useradd --system --gid app --create-home --home-dir /home/app app

WORKDIR /app

# Copy installed Python packages from builder
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# Copy application source (matches what app.py expects on sys.path)
COPY tradingagents/ ./tradingagents/
COPY web/backend/ ./web/backend/
COPY web/__init__.py ./web/
COPY cli/ ./cli/
COPY main.py ./
COPY .env.example ./.env.example

# Create writable dirs for SQLite db, eval results, results, and web static/templates
RUN mkdir -p /app/db /app/eval_results /app/results /app/web/static /app/web/templates \
&& chown -R app:app /app

USER app

EXPOSE 8000

# Healthcheck: hit /api/config (unauthenticated public config endpoint,
# returns 200 JSON). The root `/` route renders a Jinja template via an old
# Starlette API (TemplateResponse(name, context)) that breaks on Starlette
# 1.x — that's a backend bug, not a Docker issue, and doesn't affect the
# real frontend (nginx serves the static export). /api/config is the
# cheapest unauthenticated liveness probe that actually works.
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=5 \
CMD curl -fsS http://127.0.0.1:8000/api/config || exit 1

# Run via uvicorn module form (matches `python web/backend/app.py` behaviour
# but gives cleaner signal handling under container runtimes).
CMD ["uvicorn", "web.backend.app:app", "--host", "0.0.0.0", "--port", "8000", "--log-level", "info"]
48 changes: 48 additions & 0 deletions devops/Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ============================================
# TradingAgents Frontend Dockerfile
# Multi-stage: build Next.js static export, serve via Nginx.
# Context: ./web/frontend (so package.json, nginx.conf.template, etc. are COPYable).
# ============================================

# ---------- Stage 1: deps ----------
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --no-audit --no-fund

# ---------- Stage 2: builder ----------
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1

# `next build` with `output: 'export'` (next.config.ts) writes static files to ./out
RUN npm run build

# ---------- Stage 3: runtime (nginx) ----------
FROM nginx:alpine AS runtime

# envsubst for the nginx config template (FRONTEND_API_BASE_URL substitution)
RUN apk add --no-cache gettext curl

# Static export output -> nginx docroot
COPY --from=builder /app/out /usr/share/nginx/html

# nginx config template + start script (envsubst on container start)
COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY start.sh /usr/local/bin/start.sh
RUN sed -i 's/\r$//' /usr/local/bin/start.sh && chmod +x /usr/local/bin/start.sh

# Default backend URL inside the docker network; override via env at runtime.
ENV FRONTEND_API_BASE_URL="http://backend:8000"

EXPOSE 80

# Healthcheck: nginx root should return 200 (static index.html).
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=5 \
CMD curl -fsS http://127.0.0.1/ || exit 1

CMD ["/usr/local/bin/start.sh"]
Loading